Для решения задачи учета траффика возможно несколько путей. В любом случае нам надо этот траффик откуда-то снять. В принципе, эта задача не представляет собой большой сложности и может быть решена различными путями. Далее с содержимым заголовков пакетов необходимо произвести действия по выделению полезной информации и ее дальнейшей записи в систему сбора статистики. Хотелось бы более подробно обсудить со знающими людьми ряд тонких моментов. Для более конкретного разговора будем считать, что целевая платформа - линукс, хотя я с удовольствием прочитаю и про другие платформы.1. Съем траффика
Я создал пакетный сокет, который прикручиваю к обрабатываемому интерфейсу. Сокет перехватывает весь пакет, начиная с заголовка ethernet. Для этого я использовал вызов recv_from, и это прекрасно работает, НО! Вызов не завершается, пока не будет перехвачен хоть один пакет. А мне хотелось бы иметь примерно такую структуру для этой части программы:flag = 1;
while( flag )
{
if( проверить_есть_ли_данные_в сокете() )
{
считать_данные_из_сокета();
}
if( пора_сбросить_данные() )
{
сбросить_данные();
}if( пора_завершить_работу() )
{
flag = 0;
}
}для реализации подобной схемы мне надо как-то узнать, есть ли данные в сокете, причем сделать это таким образом, чтобы функция не ждала эти данные вечность. Я почитал маны и нарыл, что похоже мне смогут помочь
функции select или poll. Вопросы: это единственный вариант или есть еще какие-либо возможности? Правильно ли я вообще понял возможность использования в данном случае этих функций? И какая из функций лучше - select или poll? Какие подводные камни есть в использовании этих функций?2. Логирование траффика
В принципе можно писать в лог информацию по каждому пакету, либо каким-либо образом группировать информацию, а потом сбрасывать в лог уже получившуюся выжимку. У каждого варианта есть свои плюсы и минусы, но мне лично больше нравится второй вариант. Хотелось бы узнать, как это делается в других программах учета траффика, и как это делается в железках.3. Разделение траффика на свой и чужой.
По какому принципу системы учета траффика делят пакеты? Если провайдер присылает мне счет за 1Гиг, то что входит в этот гиг - только данные пакетов или и их заголовки? И если входят заголовки, то до какого уровня.
Я решил, что не должны учитываться заголовки канального уровня, но должны сетевого - насколько это правильно?
---------------------------------------------------------------
ethhdr | ip header | data |
---------------------------------------------------------------
^ ^
| |
| ------ платит клиент
------------ Плачу яЗаранее спасибо за ваши ответы.
>1. Съем траффика
-- skip --
> Я почитал маны и нарыл, что похоже мне смогут
>помочь
>функции select или poll. Вопросы: это единственный вариант или есть еще какие-либо
>возможности? Правильно ли я вообще понял возможность использования в данном случае
>этих функций? И какая из функций лучше - select или poll?
>Какие подводные камни есть в использовании этих функций?Лучший вариант - select, все ж это просто обёртка над системным вызовом
>2. Логирование траффика
>В принципе можно писать в лог информацию по каждому пакету, либо каким-либо
>образом группировать информацию, а потом сбрасывать в лог уже получившуюся выжимку.
>У каждого варианта есть свои плюсы и минусы, но мне лично
>больше нравится второй вариант. Хотелось бы узнать, как это делается в
>других программах учета траффика, и как это делается в железках.Когда передо мной стояла походая задача, то в программе накапливал статистику в сбалансированном дереве (man tsearch) и по внешноему сигналу
сбрасывал в БД. Если кидать данные по каждому пакету, то программа может просто не успевать на больщом потоке и к тому-же post-обработка будет ооочень длительной за счёт объема.>3. Разделение траффика на свой и чужой.
>По какому принципу системы учета траффика делят пакеты? Если провайдер присылает мне
>счет за 1Гиг, то что входит в этот гиг - только
>данные пакетов или и их заголовки? И если входят заголовки, то
>до какого уровня.
>Я решил, что не должны учитываться заголовки канального уровня, но должны сетевого
>- насколько это правильно?Уточните у поставщика услуг (у которого Вы берете трафик), за что он берет деньги, а то они (поставщики) разные бывают ;-) Хотя учитывать заголовки канала это уж слишком.
P.S. В линуксе считать трафик лучше через ULOG/netlink - в этом случае вы сможете интеллектуально разделять и фильтровать трафик к подсчёту, например отфильтровывать всякие сканы через snort и более удачно избегать переполнений.
>Лучший вариант - select, все ж это просто обёртка над системным вызовом
Обязательно попробую. Мне он тоже понравился, только вот опыта использования его у меня ноль. Положительный момент - это то, что есть не только man select но и man select_tut>Когда передо мной стояла походая задача, то в программе накапливал статистику в
>сбалансированном дереве (man tsearch) и по внешноему сигналу
>сбрасывал в БД. Если кидать данные по каждому пакету, то программа может
>просто не успевать на больщом потоке и к тому-же post-обработка будет
>ооочень длительной за счёт объема.
Похоже, что разные люди решают схожие проблемы одинаково :) Я вот тоже решил использовать двоичное дерево, только у меня оно самописное и никаких мер по балансировке я пока не предпринимал. По tsearch обязательно почитаю.>Уточните у поставщика услуг (у которого Вы берете трафик), за что он
>берет деньги, а то они (поставщики) разные бывают ;-) Хотя учитывать
>заголовки канала это уж слишком.
Да, поставщики бывают разные. У своего я конечно уточню, но мне интересно, как в этом отношении обстоит дело у других людей. Кстати, наш поставщик считает железкой (по моему какая-то киска), потому меня и интересует вопрос насчет железок.>P.S. В линуксе считать трафик лучше через ULOG/netlink - в этом случае
>вы сможете интеллектуально разделять и фильтровать трафик к подсчёту, например отфильтровывать
>всякие сканы через snort и более удачно избегать переполнений.ULOG - это одна из возможностей netfilter. А вот что за зверь netlink?
А зачем мне фильтровать сканы? Если человек меня сканит - то пусть он за это заплатит :)
>ULOG - это одна из возможностей netfilter. А вот что за зверь netlink?
>А зачем мне фильтровать сканы? Если человек меня сканит - то пусть он за >это заплатит :)
netlink - это просто способ общения пользователя с ядром(с тем-же netfilter`ом) используя интерфейс сокета..
(сорри если формулировка кривовата - все-же 2 ночи..)
man netlink спасёт отца русской демократии ;-) Конечно это чуть сложнее,
чем raw-сокет, но оно того стоит..Тем-же образом можно синхронизировать локальные таблицы маршрутов (оценок) с ядерными etcи еще один плюс ULOG - можно назначать и передавать префиксы..например для всего что шейпится поставить prefix=shaped и получить детальную статистику об эффективности. Вообщем - огромный простор для фантазии
А фильтр нужен как-раз из-за накопления статистики..Например если считать ключевой информацией пару - ip отправителя/ip получателя, то лёгкий флуд с подстановкой адресов может к DDOS или краху системы. То-же для одного адреса, только чуть дольше.
Да, почитал мельком man netlink и понял, что штука и впрямь весьма стоящая, только разбираться с ней, чувствую, будет весьма сложно.В качестве ключа я хочу использовать IP - адрес получателя, т.е. считаться будет только входящий траффик. Эти адреса будут узлами дерева, а в каждом узле uint64_t счетчик. Сначала я вообще хотел учитывать весь траффик, но потом столкнулся с проблеммой двойного обсчета: допустим хост А посылает серверу В пакет. Тогда для сервера этот пакет будет записан во входящий траффик, а для хоста этот же пакет будет записан в исходящий траффик. Тоесть пакет получается считается два раза. Как рашить эту проблему я еще не придумал.
Проблема подмены IP - адресов меня больше беспокоит в другом плане - если мы привязываем статистику к какому-либо адресу, то у пользователя появляестя дикое желание прописать себе чужой IP, дабы не платить за инет. Но этот вопрос уже уходит за рамки программы подсчета траффика.
двойной обсчёт - это вообще очень правильно на самом деле.
в вашем случае должно сходится исходящий_трафик_хоста=входящий_трафик_от_сервера
(прим. насколько знаю в бухгалтерии такое применяют, чтобы избегать ошибок)
пока-что нет распространнённых систем согласованного подсчёта трафика на двух концах соединений, если Вы хотите написать что-то подобное, то чем смогу-помогу..max_kma (at) mail (dot) ru
sorry, но на письмо смогу ответить только дня через 2-3 - командировка
Я вообще хочу сделать простую для использования, безопасную в работе и понятную в настройке и обслуживании систему - благо это надо и мне, и многим другим людям. Спасибо за мыло - когда/если у меня что-то выйдет из этой затеи - пошлю Вам исходники "на экспертизу" :) На данный момент все еще очень сырое, и показывать это кому-либо несколько рановато. Вообще же надеюсь, что потом мое творчество кому-нибудь пригодиться.
Пишу похожую программу, так вот при большом потоке трафика (5-10 мегабит) и юзеров 100 получаем что дерево может вырождаться (происходит это очень быстро) в список и нахождение нужно узла в дереве отнимает много процесорного времени. Далее я переделал это с использованием AVL дерева, но в данном случае накладными есть раходы на правые и левые повороты (балансировку). Поискав в инете как это можно сделать еще эффективнее наткнулся на red-black деревья которые больше всего подходят в данном случае . Реализацию вставки, удаления узлов в дерево можно взять в программе cnupm на сайте разработчика http://pdp-11.org.ru/~form/cnupm .
У меня щас другая идея: есть билинг nibs (nibs.net.ua) встала необходимость ввести тарифные зоны (ua-ix, бугор, пиринг и тд). Сейчас все построено по такой схеме (от юзера к серверу огранизуется vpn тунель по протоколу ppp), раз в 30 сек ppp отсылает alive пакет в котором приходит информация о количестве потребляемого траффика, далее все это отдается radius серверу который собственно и обсчитывает по нужному тарифному плану и вычитается разница с баланса. Так вот, начал копаться в исходниках pppd и понял что разделение по тарифным зонам нужно пихать в ядро в модуль ppp_generic. Потом нужно дописать код pppd чтобы он принимал с файла список ua-ix сетей (в иделале написать BGP клиента) передавал их в модуль ppp_generic который уже собственно и занимался разделением (организовать отдельные счетчики uaix_octets_in, uaix_octets_out или в общем случае масив счетчиков для каждой тарифной зоны). Потом все это дело передать в userspace pppd который через radius plugin будет передавать radius серверу где будет происходить его обсчет.PS: может кто-то делал уже подобное поделитесь опытом или подход неправильный тогда предложите свой вариант ?
когда нужно было разделять трафик по тарифным зонам,
я его делил на уровне маршрутизации..
всё достаточно просто и весь механизм определения зоны уже есть в ядре ;-)
вообщем man ip; man iptables,
и не привязываясь к несущему протоколу ;-)
Да, если дерево будет вырождаться, то прийдется с этим бороться. Хотя в моей реализации я таких проблемм пока не предвижу (возможно тут я не прав). Вообще про всяческие деревья и вообще про алгоритмы есть здоровские книги, в частности "Фундаментальные алгоритмы на С++".
По поводу разделения по зонам - я надеюсь реализовать следующий механизм: есть дерево адресов внутренних серверов, к которым тарификация очень дешевая, и есть система перехвата пакетов, о которой я уже писал. Приходит пакет и мы первым делом ищем, какой у нас адрес источника. Если есть совпадение с адресом в дереве серверов, то заносим данные по пакету в дерево внутреннего оплачиваемого траффика, а если нет - в дерево внешнего траффика. Периодически данные сбрасываются на диск, а дерево очищается. Еще у меня возникла такая идея: надо сделать список адресов наиболее популярных серверов, и на этапе инициализации дерева заносить их в самое его начало. Это позволит улучшить среднее время поиска элемента (по крайней мере мне так кажется - математически доказывать я это не возьмусь, так как в математике не силен :( ).
По поводу протоколов - считаю, что для подсчета траффика необходимо, чтобы программа понимала пакеты IP (кто бы сомневался ;)), PPPoE и IP c VLAN. Остальные протоколы на мой взгляд не так важны.
>когда нужно было разделять трафик по тарифным зонам,
>я его делил на уровне маршрутизации..
>всё достаточно просто и весь механизм определения зоны уже есть в ядре
>;-)
>вообщем man ip; man iptables,
>и не привязываясь к несущему протоколу ;-)про это мне все известно, но как мне реализовать разделение трафика по каждому пользователю если у меня один канал на провайдера и он ua-ix отдает по одной цене, а бугор по другой. На каждого пользователя открывается ppp тунель. iptables ты врядли заставишь отдавать статистику радиусу, а если и заставишь то только через скрипты (например на perl), но это усложняет саму систему учета. А если пропатчить ppp_generic.c и добавить в него пару счетчиков траффика и при приеме/передачи фрейма ppp анализировать ip_hdr: ip_src, ip_dst, protocol (в sk_buff.h есть указатель на ip_hdr ).
Может я что-то не так понял, но зачем городить огород с raw-сокетами? iptables же всё уже считает. При этом решается проблема с NAT. Стоит только прописать соответствующие правила в -t FORWARD. Я написал небольшую программку-парсер, которая брала статистику из иптаблеса и ложила в отчёт (без заморочек с БД) - благо сеть домашняя. Всё это дело обёртывается в скрипты и получается довольно универсвльно ИМХО. Юникс-вэй - не изобретать то, что уже изобретено :-)
Насчёт подмены ипов - в iptables можно написать:
iptables -A INPUT -i $I_LAN -s 10.0.0.1 -m mac --mac-source ! xx:xx:xx:xx:xx:xx -j DROP
И те, кто только осмелиться покуситься на мой ип идут лесом. Опять же для малых сетей (ибо правила разрастутся, но а как по-другому?).
Возможно select лучшее решение для данного случая. Альтернативой может быть отдельная нитка на чтение сокета и дальше семафорами... Такие решения несколько сложнее, но зато нет пустого хождения по циклам.
>Возможно select лучшее решение для данного случая. Альтернативой может быть отдельная нитка
>на чтение сокета и дальше семафорами... Такие решения несколько сложнее, но
>зато нет пустого хождения по циклам.Если я правильно понял, то у нас два потока
1 поток - прием данных с сокета
2 поток - обработка команд, поступающих от пользователя или планировщика.Второй поток должен управлять работой первого.
Не уверен, что это решение так хорошо. Вопрос тут в том, как прервать прослушку в первом потоке. Допустим мы слушаем одной из разновидностей recv. Как тогда завершить recv, если не пришло пакета? Лично мне не известен ответ на этот вопрос. Может ли второй поток временно приостановить работу первого? Если может, то в принципе можно реализовать такую схему. Но я уже сделал прослушку с использованием select, так что это оставлю на будущее. Сейчас я сделал по такому принцину:
flag = 1;
while( flag )
{
подготовка к работе select;
retval = select();
// В сокете есть данные?
if( retval > 0 )
{// да
flag = 0;
Установить флаг успешного завершения функции;
}
else
if( retval < 0 )
{ // Выход по случаю ошибки
if( errno != EINTR )
{
flag = 0;
Выйти с неустановленным флагом успеха
}
}
else // Выход по таймауту
{
// Тут проверяется флаг продолжения работы демона.
// Если он сброшен, то выйти - flag = 0;
}
}
Вызов recv(как и многие другие вызовы) может быть прерван при доставке какого-либо сигнала, только этот сигнал не должен игнорироваться, и не должен стоят флаг SA_RESTART, и многие сигналы с действиями по умолчанию приводят к завершению процесса, так что их лучше обрабатывать, в этом случае вызов recv будет прерван с errno==EINTR. А еще можно установить в fcntl флаг O_NONBLOCK на дескрипторе, тогда нет данных для чтения - вызов recv будет прерван с EAGAIN. Вот только тут я неуверен, что fcntl можно использовать на сокете, наверное надо сначала его fdopen, а потом read оттуда.:) А может и нет. Надеюсь кто-нибудь компетентный прояснит ситуацию.
> Сейчас я сделал по такому принцину...и чем этот принцип отличается от recv ? ;-)
>>Возможно select лучшее решение для данного случая. Альтернативой может быть отдельная нитка
>>на чтение сокета и дальше семафорами... Такие решения несколько сложнее, но
>>зато нет пустого хождения по циклам.
>
>Если я правильно понял, то у нас два потока
>1 поток - прием данных с сокета
>2 поток - обработка команд, поступающих от пользователя или планировщика.
>
>Второй поток должен управлять работой первого.
>Не уверен, что это решение так хорошо. Вопрос тут в том, как
>прервать прослушку в первом потоке. Допустим мы слушаем одной из разновидностей
>recv. Как тогда завершить recv, если не пришло пакета? Лично мне
>не известен ответ на этот вопрос. Может ли второй поток временно
>приостановить работу первого? Если может, то в принципе можно реализовать такую
>схему. Но я уже сделал прослушку с использованием select, так что
>это оставлю на будущее. Сейчас я сделал по такому принцину:
>flag = 1;
>while( flag )
>{
> подготовка к работе select;
> retval = select();
> // В сокете есть данные?
> if( retval > 0 )
> {// да
> flag =
>0;
> Установить флаг
>успешного завершения функции;
> }
> else
> if( retval < 0 )
> { // Выход по случаю ошибки
> if( errno
>!= EINTR )
> {
>
> flag = 0;
>
> Выйти с неустановленным флагом успеха
> }
> }
> else // Выход по таймауту
> {
> // Тут проверяется
>флаг продолжения работы демона.
> // Если он
>сброшен, то выйти - flag = 0;
> }
>}
Не так. Первый поток управляет вторым. Т.е. выставляет семафор когда что-то прочитал и может передать второму. А прерывать его вообще не надо, пускай блокируется сколько хочет. OS сама переключит контекст. Тут могут быть проблемы с приоритетами или если данные идут постоянно, но если данные приходят редко то все OK.
Я уже второй год разрабатываю биллинг. Обсчитывает сеть из 400 пользователей. Из вкусностей - обсчет всех пакетов по одному правилу iptables (-A FORWARD -j QUEUE) с использованием библиотеки libipq,
контроль подмены MAC, различные тарифные планы и т.д. Работает с MySQL.
Размер компилированного файла 40 Кб, который делает всю работу (например в NetUP UTM 5.0 размер загрузочного файла 10 Мб :) сумашествие)
Ищу желающих доработать его, и создать какой-нить GNU биллинг с открытыми исходниками.
>Я уже второй год разрабатываю биллинг. Обсчитывает сеть из 400 пользователей. Из
>вкусностей - обсчет всех пакетов по одному правилу iptables (-A FORWARD
>-j QUEUE) с использованием библиотеки libipq,
>контроль подмены MAC, различные тарифные планы и т.д. Работает с MySQL.
>Размер компилированного файла 40 Кб, который делает всю работу (например в NetUP
>UTM 5.0 размер загрузочного файла 10 Мб :) сумашествие)
>Ищу желающих доработать его, и создать какой-нить GNU биллинг с открытыми исходниками.
>
Посмотреть можно?
На чем писал?