Добавляем лимитирование трафика и ограничение пропускной способности канала.
Лимитируем трафик
Модернизируем нашу базу:
ALTER TABLE `users` ADD COLUMN `status` char(1) NOT NULL DEFAULT '1';
ALTER TABLE `users` ADD COLUMN `speed` int(11) NOT NULL DEFAULT '0';
ALTER TABLE `users` ADD COLUMN `traf_limit` bigint(20) NOT NULL DEFAULT '0';
ALTER TABLE `users` ADD COLUMN `traf_remain` bigint(20) NOT NULL DEFAULT '0';
Поле status определяет текущий статус аккаунта (1 — включен, 0 — заблокирован), speed — ограничение скорости в Kbit’ах (0 — без ограничения). Поля traf_limit и traf_remain лимит трафика в байтах и оставшееся его количество соответственно, если traf_limit равен 0, то считаем, что лимита нет.
Так как теперь при подключении нам нужно отсеивать пользователей, лимит которых закончился или их аккаунты в статусе «блокирован», подправим конфиг freeradius’a. В файле /etc/freeradius/sql.conf замените строчки
authorize_check_query = "SELECT id, login, 'User-Password' AS \"Attribute\", `password` AS \"Value\", '==' AS \"op\" FROM users WHERE login = '%{SQL-User-Name}'"
authorize_reply_query = "SELECT id, login, 'Framed-IP-Address' as \"Attribute\", ip as \"Value\", ':=' as \"op\" FROM users WHERE login = '%{SQL-User-Name}'"
authorize_group_check_query = "SELECT '1' as \"id\",'default' AS \"GroupName\", 'Auth-Type' as \"Attribute\", CASE WHEN status='1' THEN 'MS-CHAP' ELSE 'REJECT' END as \"Value\", ':=' as \"op\" FROM users WHERE login='%{SQL-User-Name}'"
на такие:
authorize_check_query = "SELECT id, login, 'User-Password' AS \"Attribute\", `password` AS \"Value\", '==' AS \"op\" FROM users WHERE login = '%{SQL-User-Name}' and status='1' and (traf_remain>0 or traf_limit=0)"
authorize_reply_query = "SELECT id, login, 'Framed-IP-Address' as \"Attribute\", ip as \"Value\", ':=' as \"op\" FROM users WHERE login = '%{SQL-User-Name}' and status='1' and (traf_remain>0 or traf_limit=0)"
authorize_group_check_query = "SELECT '1' as \"id\",'default' AS \"GroupName\", 'Auth-Type' as \"Attribute\", CASE WHEN status='1' THEN 'MS-CHAP' ELSE 'REJECT' END as \"Value\", ':=' as \"op\" FROM users WHERE login='%{SQL-User-Name}' and status='1' and (traf_remain>0 or traf_limit=0)"
и перезапустите freeradius.
Теперь модернизируем скрипт парсера, код:
#!/usr/bin/perl
use DBI;
# функция для преобразования айпи из формы ххх.ххх.ххх.ххх в десятичную
sub inet_aton {
my @addr = split(/\./,$_[0]);
my $dec = 0;
for($n = 3; $n >= 0; $n–) {
$dec += ($addr[-$n-1] << 8 * $n);
}
return $dec;
}# определяем имя БД, пользователя и пароль
my $db_name = “ulogdb”;
my $db_user = “ulog”;
my $db_pass = “1234″;# путь к лог-файлу
$account_log = “/var/log/ulog-acctd/account.log”;# подключаемся к нашей базе
my $DBH = DBI->connect(“DBI:mysql:$db_name:localhost”,$db_user,$db_pass) or die “Error connecting to database”;# если скрипт запущен с параметром –set-limits, сбрасывает счетчики трафика пользователей
if ($ARGV[0] eq “–set-limits”) {
print “$ARGV[0]\n”;
# если 1, то неизрасходованный трафик переносится на следующий месяц
my $move_unused = 1;
if ($move_unused) {
$STH = $DBH->prepare(“update users set traf_remain=traf_remain+traf_limit where traf_limit”);
} else {
$STH = $DBH->prepare(“update users set traf_remain=traf_limit where traf_limit”);
}
$STH->execute; $STH->finish;
exit;
}# получаем список пользователей в связке ip+id_user
my $STH = $DBH->prepare(“select ip,id from users”);
$STH->execute;
while (@tmp = $STH->fetchrow_array()) {
$users{$tmp[0]} = $tmp[1];
}
$STH->finish;# получаем список сетей
my $STH = $DBH->prepare(“select prio,firstip,lastip,id from zones order by prio”);
$STH->execute;
while (@tmp = $STH->fetchrow_array()) {
$zones[$tmp[0]] = [$tmp[1], $tmp[2], $tmp[3]];
}
$STH->finish;# делаем временную копию лога и очищаем оригинальный файл
system “cp $account_log /tmp/ulog-parser.tmp && cat /dev/null > $account_log”;
open LOGFILE,”< /tmp/ulog-parser.tmp”;
while (<LOGFILE>) {
chomp;# переменную $saddr пока не используем,
# она пригодится позже($ts,$saddr,$daddr,$bytes) = split /\t/;
# создаем новую временную метку, необходимо для агрегирования
# статистки пользователя за определенный интервал времени
# в одну запись. интервалом будем считать 1 минуту$ts = $ts – $ts % 60;
# сопоставляем айпи из лога со списком пользователей
# если айпи имеется в базе – наш клиент
# массив со статистикой имеет древовидную структуру:
# метка времени -> id пользователя -> полученный трафикif (exists($users{$daddr})) {
# получаем идентификатор зоны трафика
$zone_id = 0;
for($i=0;$i>=$zones;$i++) {
$nip = inet_aton($saddr);
if ($zones[$i][0] <= $nip and $zones[$i][1] >= $nip) {
$zone_id = $zones[$i][2];
last;
}
}
$data{$ts}{$users{$daddr}}{$zone_id} += $bytes;
}
}
close LOGFILE;
unlink(“/tmp/ulog-parser.tmp”);# немного оптимизировал запрос, спасибо хабраюзеру mgyk за подсказку :)
my $STH = $DBH->prepare(“insert into data (id_user,id_zone,ts,bytes) values(?,?,?,?) on duplicate key update bytes=bytes+?”);
my $STH_LIMIT = $DBH->prepare(“update users set traf_remain=traf_remain-? where id=? and traf_limit”);
# проходим по всему массиву статистики вложенным циклом
#
for $ts (keys %data) {
for $id_user (keys %{$data{$ts}}) {
for $id_zone(keys %{$data{$ts}{$id_user}}) {
$STH->execute($id_user,$id_zone,$ts,$data{$ts}{$id_user}{$id_zone},$data{$ts}{$id_user}{$id_zone});
$STH->finish;
# вычитаем из кол-ва оставшегося трафика пользователя текущий трафик
$STH_LIMIT->execute($data{$ts}{$id_user}{$id_zone},$id_user);
$STH_LIMIT->finish;
}
}
}# выберем из базы пользователей, у которых закончился лимит
$STH = $DBH->prepare(“select ip from users where traf_limit>0 and traf_remain<=0″);
$STH->execute;
while (($ip) = $STH->fetchrow_array) {
my $lnk = `/sbin/ip addr show|/bin/grep $ip`;
$lnk =~ m/^.+(ppp[0-9]+)$/;
# разрываем сессию
system(“/bin/kill `cat /var/run/$1.pid`”);
}
# отключаемся от БД
$DBH->disconnect;
Если у Вас есть пользователи с лимитированным трафиком, добавьте в crontab скрипт парсера с парметром –set-limits на первое число месяца, тем самым каждый месяц пользователям будет начисляться трафик. Чтобы своевременно отключать тех, у кого лимит уже закончился, сделайте интервал запуска парсера раз в 1-2 минуты. Например, так:
* * * * * root /usr/bin/ulog-parser.pl
1 0 1 * * root /usr/bin/ulog-parser.pl --set-limits
каждую минуту парсим лог и в 00:01 1-го числа каждого месяца обновляем лимиты трафика. Проще некуда (:
Режем скорость
Добавляем в iptales правило:
iptables -t mangle -A FORWARD -d 10.1.0.0/24 -j MARK --set-mark 0x1
Маркируем все пакеты, идущие на адреса наших пользователей.
При инициализации любой ppp-сессии запускаются скрипты, находящиеся в /etc/ppp/ip-up.d. Нам это приходится как нельзя к стати. Создадим в этой директории скрипт, который при подключении пользователя будет ограничивать пропускную способность его интерфейса, я назвал его set_speed:
#!/usr/bin/perl
use DBI;
my $db_name = “ulogdb”;
my $db_user = “ulog”;
my $db_pass = “1234″;my ($ip,$iface) = @ARGV[4,0];
my $DBH = DBI->connect(“DBI:mysql:$db_name:localhost”,$db_user,$db_pass) or die “Error connecting to database”;
my $STH = $DBH->prepare(“select speed from users where ip=’$ip’”);
$STH->execute;
my ($speed) = $STH->fetchrow_array;
$STH->finish;if ($speed) {
system(“/sbin/tc qdisc add dev $iface root handle 1: htb”);
system(“/sbin/tc class add dev $iface classid 1:1 htb rate ${speed}kbit”);
system(“/sbin/tc filter add dev $iface protocol ip handle 1 fw classid 1:1″);
}
$DBH->disconnect;
В нем тоже нужно указать имя пользователя базы и пароль. Вообще-то, правильней было бы хранить их в отдельном конфиге, но я не стал заморачиваться с реализацией, Вы можете сами сделать это (:
Не забудьте добавить атрибут +x к скриптам.
Взято с хабра. Часть 1. Часть 2.
Постовой
Удобные и красивые наливные полы – лучшее решения для вашего дома.
Здесь можно скачать лучшие обои для рабочего стола. Огромный выбор, качественные обои.
One Comment