Выдача ip в зависимости от того, к какому NAS подключен абонент

Матеріал з NoDeny
Перейти до навігації Перейти до пошуку

Статья актуальна как для dhcp так и для pppoe, настроеных на использование протокола radius.

Суть заключается в том, что dhcp или pppoe-сервер передает на radius свой идентификатор. Например, мак-адрес, либо свой номер, либо любой иной идентификатор. Какой именно - разберем позже. Сначала нужно создать в mysql функцию, которая будет выдавать ip из разных пулов в зависимости от идентификатора. Этот идентификатор назовем «тегом».

Предварительно, в админке заходим в настройки ip пулов, выбираем, скажем пул 10.0.0.2 ... 10.0.4.254, устанавливаем тип «динамический», а в поле «теги» вписываем слово «nas1». Для другого пула теги в значение «nas2» и т.д.

Создаем mysql процедуру get_ip_by_tag:

DROP FUNCTION IF EXISTS `get_ip_by_tag`;
DELIMITER $$
CREATE FUNCTION `get_ip_by_tag` ( user_id INTEGER UNSIGNED, tag VARCHAR(64) )
    RETURNS VARCHAR(15) NO SQL
BEGIN
    DECLARE user_ip VARCHAR(15);
    DECLARE real_ip VARCHAR(15) DEFAULT 0;
    DECLARE row_cnt INTEGER;
    DECLARE ip_id INTEGER;
    DECLARE tries INTEGER DEFAULT 30;
    DECLARE id_min INTEGER;
    DECLARE id_max INTEGER;

    SELECT INET_NTOA(ip) INTO user_ip FROM ip_pool
        WHERE uid = user_id AND type='static' LIMIT 1;
    IF( user_ip IS NOT NULL ) THEN RETURN user_ip; END IF;

    SELECT 1 INTO real_ip FROM users_services WHERE uid = user_id AND tags LIKE '%,realip,%';

    SELECT id, INET_NTOA(ip) INTO ip_id, user_ip FROM ip_pool
        WHERE uid = user_id AND type = 'dynamic' AND realip = IF(real_ip>0,1,0)
            AND tags LIKE CONCAT('%,', tag, ',%')
        LIMIT 1;

    IF( ip_id IS NOT NULL) THEN
        UPDATE ip_pool SET `release` = UNIX_TIMESTAMP() + 3600
            WHERE id = ip_id AND uid = user_id;
        SELECT ROW_COUNT() INTO row_cnt;
        IF( row_cnt > 0 ) THEN RETURN user_ip; END IF;
    END IF;

    SELECT MAX(id), MIN(id) INTO id_max, id_min
        FROM ip_pool
        WHERE type = 'dynamic' AND realip = IF(real_ip>0,1,0)
            AND tags LIKE CONCAT('%,', tag, ',%');

    sel_ip: WHILE tries > 0 DO
        SELECT id, INET_NTOA(ip) INTO ip_id, user_ip
            FROM ip_pool
            WHERE uid = 0
                AND id >= (CEIL(RAND() * (id_max - id_min)) + id_min)
                AND id <= id_max
                LIMIT 1;
        IF( user_ip IS NOT NULL) THEN
            UPDATE ip_pool SET uid = user_id, `release` = UNIX_TIMESTAMP() + 3600
                WHERE id = ip_id AND uid = 0;
            SELECT ROW_COUNT() INTO row_cnt;
            IF( row_cnt > 0 ) THEN RETURN user_ip; END IF;
            SET tries = tries - 5;
        END IF;
        SET tries = tries - 1;
    END WHILE;

END$$
DELIMITER ;

Проверим:

SELECT get_ip_by_tag(371, 'nas1');

где 371 - id абонента. У него не должны быть подключены статические ip!

Должны получить ip из пула для nas1. Проверим так же и для nas2.

Теперь изменим процедуру radreply - она возвращает dhcp или pppoe серверу ip в зависимости от тега. В качестве логина в dhcp используется мак-адрес абонента. Процедура radreply для dhcp:

DROP PROCEDURE IF EXISTS `radreply`;
DELIMITER $$
CREATE PROCEDURE `radreply`( IN login VARCHAR(64), tag VARCHAR(64) )
BEGIN
    DECLARE usr_mac VARCHAR(12);
    DECLARE usr_ip VARCHAR(15);
    DECLARE usr_id INT;
    SELECT REPLACE(login, ':', '') INTO usr_mac;
    SELECT uid INTO usr_id FROM mac_uid WHERE mac=usr_mac;
    IF usr_id IS NOT NULL AND usr_id>0 THEN
        SELECT get_ip_by_tag(usr_id, tag) INTO usr_ip;
        UPDATE mac_uid SET ip=0 WHERE ip=INET_ATON(usr_ip) AND uid<>usr_id;
        UPDATE mac_uid SET ip=INET_ATON(usr_ip), time=UNIX_TIMESTAMP() WHERE uid=usr_id;
    ELSE
        START TRANSACTION;
        SELECT INET_NTOA(ip) INTO usr_ip FROM ip_pool
            WHERE uid=0 AND type='dynamic' AND `release` < UNIX_TIMESTAMP()
            AND tags LIKE CONCAT('%,', tag ,',%')
            ORDER BY RAND() LIMIT 1 FOR UPDATE;
        INSERT INTO mac_uid VALUES(
            NULL, usr_mac, INET_ATON(usr_ip), 0, UNIX_TIMESTAMP(), 0, 0, 0, '')
        ON DUPLICATE KEY
            UPDATE ip=IF(ip>0,ip,INET_ATON(usr_ip)), time=UNIX_TIMESTAMP();
        COMMIT;
        SELECT INET_NTOA(ip) INTO usr_ip FROM mac_uid WHERE mac=usr_mac;
        UPDATE ip_pool SET `release` = UNIX_TIMESTAMP() + 3600
            WHERE ip = INET_ATON(usr_ip);
    END IF;
    SELECT NULL, login, 'Framed-IP-Address', usr_ip, '=';
    SELECT NULL, login, 'Session-Timeout', '600', '=';
END$$
DELIMITER ;
DROP PROCEDURE IF EXISTS `radupdate`;
DELIMITER $$
CREATE PROCEDURE `radupdate`(IN login VARCHAR(64), IN ip VARCHAR(16),
    IN properties VARCHAR(255), tag VARCHAR(64))
BEGIN
    DECLARE usr_mac VARCHAR(12);
    DECLARE usr_ip VARCHAR(15);
    DECLARE usr_id INT;
    SELECT REPLACE(login, ':', '') INTO usr_mac;
    SELECT uid INTO usr_id FROM mac_uid WHERE mac=usr_mac;
    IF usr_id IS NULL
    THEN
        SELECT ip INTO usr_ip;
    ELSE
        SELECT get_ip_by_tag(usr_id, tag) INTO usr_ip;
    END IF;
    CALL set_auth(usr_ip, CONCAT('mod=pppoe;', REPLACE(properties,':','')));
END$$
DELIMITER ;

Проверка:

CALL radreply('00:11:22:33:44:77', 'nas1');

- здесь 00:11:22:33:44:77 - мак абонента, должен быть прописан в его учетке.

В ответ на вышепреведенный запрос мы должны получить подобное:

+------+-------------------+-------------------+-----------+---+
| NULL | login             | Framed-IP-Address | usr_ip    | = |
+------+-------------------+-------------------+-----------+---+
| NULL | 00:11:22:33:44:77 | Framed-IP-Address | 10.0.1.19 | = |
+------+-------------------+-------------------+-----------+---+
+------+-------------------+-----------------+-----+---+
| NULL | login             | Session-Timeout | 600 | = |
+------+-------------------+-----------------+-----+---+
| NULL | 00:11:22:33:44:77 | Session-Timeout | 600 | = |
+------+-------------------+-----------------+-----+---+

Теперь необходимо настроить dhcp сервер чтобы он сообщал radius-у свой идентификатор. Можно использовать мак-адрес этого сервера либо другой параметр, например mikrotik в параметре Called-Station-Id посылает параметр Name, указываемый в его dhcp-настройках.

Меняем конфиг /usr/local/etc/raddb/sql.conf:

        authorize_check_query = "call radcheck('%{User-Name}')"
        authorize_reply_query = "call radreply('%{User-Name}', '%{Called-Station-Id}')"
        postauth_query = "call radupdate('%{User-Name}','%{reply:Framed-IP-Address}','nas=%{NAS-IP-Address}', '%{Called-Station-Id}')"
        accounting_update_query = "call radupdate('%{User-Name}','%{Framed-IP-Address}','nas=%{NAS-IP-Address}', '%{Called-Station-Id}')"

Запускаем Radius в режиме debug:

radiusd -X

и смотрим какие sql выполняются:

   ...
[sql] 	expand: call radreply('%{User-Name}', '%{Called-Station-Id}') -> call radreply('00:11:22:33:44:55', 'nas1')
   ...