Simult Chek в Radius

Simult Chek в Radius

По статье комрада sirmax: 24 июня 2010.
В сети используется VPN (accel-pptp) с авторизацией на центральном radius-сервере (freeradius). При попытке авторизации 2 и более клиентов с одинаковыми UserName может возникнуть ситуация когда пытающиеся авторизоваться (или часть из них) пройдут авторизацию успешно.

Это связано с особенностью провеки одновременности подключений — запрос

1
simul_count_query = "SELECT COUNT(*) FROM ${acct_table1} WHERE UserName=\'%{SQL-User-Name}\' AND AcctStopTime = 0"

проверяет только наличие сессий в таблице acct_table1 (обычно radacct). Сессии в таблице acct_table1 создаются только при получении Acct-пакета от NAS-а (VPN-серверов в моем случае ). В результате, возможна ситуация когда из-за различных причин, как например, нагрузка NAS-a или потерь в сети, возникает некоторый промежмежуток времени, в течении которого возможно авторизоваться повторно.
Следующяя схема илюстрирует эту ситуацию: С UserName=test пробуют соединиться 2 различных клиента.

1
2
3
4
5
6
7
8
9
10
mysql> SELECT * FROM radcheck WHERE username='test';
+-------+-----------+------------------+----+-----------+
| id    | UserName  | Attribute        | op | VALUE     |
+-------+-----------+------------------+----+-----------+
|  9295 | test      | Pool-Name        | := | ippool_1  |
|  9294 | test      | Password         | == | password  |
|  9293 | test      | Auth-TYPE        | := | MS-CHAP   |
|  9296 | test      | Simultaneous-USE | := | 1         |
+-------+-----------+------------------+----+-----------+
4 ROWS IN SET (0.00 sec)
Время («кванты») Первый клиент (UserName=test) Второй клиент (UserName=test) Radius-Сервер
1 Установка соединения (ppp)
2 Запрос к радиусу Auth-Request Установка соединения (ppp) Получение запроса от Клиента 1, проверка атрибутов, Вычисление значения Simultaneous-Use (=1 т.к. пользователь не был подключен, отправка Access-Accept на NAS)
3 Получение Access-Accept, авторизация клиента, создание интерфейса, и т.п. Запрос к радиусу Auth-Request Получение запроса от Клиента 2, проверка атрибутов, Вычисление значения Simultaneous-Use (=1 т.к. сессия первого клиента еще не попала в acct_table1)
4 NAS формирует пакет Acct-Start, задержка с отправкой из-за нагрузки на CPU Получение Access-Accept, авторизация клиента, создание интерфейса, и т.п. Ожидание Acct-Start от NAS
5 Отправка Acct-Start Отправка Acct-Start Занесенеее 2 сессий в acct_table1

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

Варианты решения

Простой

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

Для внесения задержки можно модифицировать запрос

1
2
3
4
authorize_check_query = "SELECT id, UserName, Attribute, Value, op \
 FROM ${authcheck_table} \
 WHERE Username = '%{SQL-User-Name}' \
 ORDER BY id"

следующим образом

1
2
3
4
authorize_reply_query = "SELECT id+sleep(FLOOR(0 + (RAND() * 10))), UserName, Attribute, Value, op \
 FROM ${authreply_table} \
 WHERE Username = '%{SQL-User-Name}' \
 ORDER BY id"

Этот запрос будет выполняться с задержкой 0-10 секунд.

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

Однако, присутвуют следующие недостатки:

  1. Существует вероятность отличная от нуля что функция RAND() вернет 2 раза одинаковые значения и запросы все же остануться «одновременными».
  2. Внесение задержки создает неудобства для абонентов (были жалобы).
  3. Слишком большое значение множителя ( более 10 секуд) может приводить к ошибкам авторизации по таймауту.

Под «одновременными» запросами авторизации подразумевается что времени между запросами окажется недостаточно для внесения данных в acct_table1 по той или инной причине.

Вариант решения основанный на транзакциях

Автор решения — voron, хотя нельзя сказать что я совсем не принимал участия в разработке ) Основная идея этого решения — при авторизации создавать в дополнительной таблице записи о «текущих сессиях авторизации», и использовать транзакции для блокировок строк таблицы.

Дополнительный процедуры и таблицы

Таблица для хранения текущих сессий авторизации

1
2
3
4
5
6
CREATE TABLE auth_sessions(
    username VARCHAR(64),
    TIME INTEGER NOT NULL,
    KEY username(`username`(64)),
    KEY TIME(`time`)
)engine=InnoDB;

Таблиа для хранения блокировок авторизующихся пользователей, единственное назначение — блокировка строк таблицы auth_sessions для нужного пользователя

1
2
3
4
5
CREATE TABLE auth_sessions_users(
    username VARCHAR(64),
    ulock INTEGER NOT NULL,
    PRIMARY KEY username(`username`(64))
)engine=InnoDB;

Таблица для логгирования (используется только при отладке)

1
2
3
4
5
6
CREATE TABLE check_simul_log
(
    UserName VARCHAR(64) NOT NULL,
    SimulCheck INTEGER NOT NULL,
    TIME INTEGER NOT NULL
)engine=InnoDB;

Процедура check_simul предназначена для того что бы одновременно проверять кроме уже существующих сессий еще и текущие сессии авторизации. Процедура блокирует таблицу auth_sessions_users.
AuthLifeTime — время жизни сесии авторизации, по истечении которого незавершенная сессия авторизации независимо от результата удалиться, ascount — auth session count, кол-во сессий сессий авторизации (активных попыток авторизоваться в текущий момент). Закомментарены запросы которые использовались для отладки.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
delimiter //
CREATE PROCEDURE check_simul(IN SQLUserName VARCHAR(64), IN AuthLifeTime INT)
BEGIN
    DECLARE ascount INTEGER;
    DECLARE radacct_count INTEGER;
    SET autocommit=0;
    START TRANSACTION;
        INSERT INTO auth_sessions_users VALUES(SQLUserName,1) ON duplicate KEY UPDATE ulock=1; -- lock
        DELETE FROM auth_sessions WHERE username=SQLUserName AND time<(UNIX_TIMESTAMP()-AuthLifeTime); -- delete too old auth sessions
        SELECT COUNT(*) INTO ascount FROM auth_sessions WHERE username=SQLUserName; -- Count active auth sessions (0 if no other auth sessions )
        INSERT INTO auth_sessions VALUES(SQLUserName,UNIX_TIMESTAMP()); -- Add current auth session
        UPDATE auth_sessions_users SET ulock=0 WHERE username=SQLUserName;
--        INSERT into check_simul_log values(SQLUserName,ascount,UNIX_TIMESTAMP(),-1);
    commit;
    SET autocommit=1;
    SELECT COUNT(*) INTO radacct_count FROM radacct WHERE UserName=SQLUserName AND AcctStopTime = 0;
--    INSERT into check_simul_log values(SQLUserName,radacct_count+ascount,UNIX_TIMESTAMP(),radacct_count);
    SELECT (radacct_count+ascount);
END;
//
delimiter ;

Удалении сессии авторизации после неуспешной по причине too many connections авторизации.
Т.е. если соединение отброшено по-тому что в acct_table1 присутвует активная сессия то добавлять попытку авторизации не нужно.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
delimiter //
CREATE PROCEDURE postauth(IN SQLUserName VARCHAR(64),IN PacketType VARCHAR(64),IN ReplyMessage VARCHAR(255) )
BEGIN
    INSERT INTO radpostauth(USER, pass, reply, DATE, ReplyMessage) VALUES (SQLUserName, 'Chap-Password', PacketType, NOW(), ReplyMessage);
    IF ((strcmp(LOWER(PacketType),"access-reject")=0) AND ((LOWER(ReplyMessage) REGEXP LOWER("You are already logged in - access denied"))=1)) THEN
        SET autocommit=0;
        START TRANSACTION;
            INSERT INTO auth_sessions_users VALUES(SQLUserName,1) ON duplicate KEY UPDATE ulock=1;
            DELETE FROM auth_sessions WHERE username=SQLUserName ORDER BY TIME DESC LIMIT 1;
            UPDATE auth_sessions_users SET ulock=0 WHERE username=SQLUserName;
        commit;
        SET autocommit=1;
    END IF;
END;
//
delimiter ;

Изменения в sql.conf

1
authorize_check_query = "CALL radius_authorize_check_query('%{SQL-User-Name}')"

(Соответсующяя часть запроса тоже вынесена в процедуру)

1
2
3
4
5
6
7
delimiter //
CREATE PROCEDURE radius_authorize_check_query(IN SQLUserName VARCHAR(255))
BEGIN
    SELECT id,UserName,Attribute,VALUE,op FROM radcheck WHERE Username = SQLUserName;
END
//
delimiter ;
1
simul_count_query = "CALL check_simul('%{SQL-User-Name}',60)"
1
postauth_query = "CALL postauth('%{User-Name}', '%{reply:Packet-Type}', '%{reply:Reply-Message:-EmptyReplyMessage}')"

Add

— показать нужный код sql.conf — убрать SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; и autocommit в 4-х местах — назначение postauth — уделение сессий авторизации при «долбёжке», это мона указать — check_simul_log энжин иннодб — неплохо бы поформатать sql, единственное что я нашёл это www.sqlinform.com, java нужна

Установка, замена, врезка замков — замена замков металлические двери.


http://blog.wel.org.ua

работаю админом, прогером сеошнегом :)

Leave a Comment

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Загрузка...
Menu Title