Undernet Radius: відмінності між версіями

Матеріал з NoDeny
Перейти до навігації Перейти до пошуку
мНемає опису редагування
 
(Не показані 92 проміжні версії цього користувача)
Рядок 1: Рядок 1:
==Добавить в пул ip поле для гостевых подключений==
<pre>
alter table ip_pool add guest varchar(60) not null default '';
alter table ip_pool add key (guest);
alter table ip_pool add last_session varchar(64) not null default '';
alter table ip_pool change `type` `type` enum(
  'static','dynamic','reserved',
  'pool_0', 'pool_1', 'pool_2', 'pool_3', 'pool_4',
  'pool_5', 'pool_6', 'pool_7', 'pool_8', 'pool_9'
) NOT NULL;
CREATE TABLE `pod_queue` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `usr_ip` varchar(16) NOT NULL DEFAULT '',
  `nas_ip` varchar(16) NOT NULL DEFAULT '',
  `session` varchar(128) NOT NULL DEFAULT '',
  `created_at` int(10) unsigned NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `created_at` (`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=1;
CREATE TABLE `rad_log` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created_at` DATETIME DEFAULT NOW(),
  `username` varchar(127) NULL,
  `nas_name` varchar(32) NULL,
  `svlan` varchar(32) NULL,
  `cvlan` varchar(32) NULL,
  `user_id` int(11) NULL,
  `user_ip` varchar(15) NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
</pre>
==Настройка NoDeny==
==Настройка NoDeny==


Рядок 7: Рядок 43:
<pre>
<pre>
ERX-Service-Activate:1+= "svc-global-ipoe(128000, 128000)"
ERX-Service-Activate:1+= "svc-global-ipoe(128000, 128000)"
ERX-Virtual-Router-Name = "brs"
</pre>
</pre>


Рядок 24: Рядок 61:
* Группы: switch
* Группы: switch
----
----
* Имя поля: SVLAN
* Имя поля: Service VLAN
* Имя поля в бд: _svlan
* Имя поля в бд: _svlan
* Тип поля: целое положительное
* Группы: switch
----
* Имя поля: Client VLAN
* Имя поля в бд: _cvlan
* Тип поля: целое положительное
* Тип поля: целое положительное
* Группы: switch
* Группы: switch
Рядок 33: Рядок 75:
* Тип поля: мак
* Тип поля: мак
* Группы: switch
* Группы: switch
==В Настроках-Dhcp/оборудование==
Имя дополнительного поля, в котором хранится мак-адрес свича установить в _mac


==Тестовые данные==
==Тестовые данные==
Рядок 96: Рядок 141:
     accounting {
     accounting {
         sql
         sql
        ok
     }
     }
     session {
     session {
Рядок 103: Рядок 149:
     post-auth {
     post-auth {
         sql
         sql
        ok
     }
     }
     Post-Auth-Type ACCEPT {
     Post-Auth-Type ACCEPT {
Рядок 125: Рядок 172:
         warnings = auto
         warnings = auto
     }
     }
     server = "localhost"
     server = "91.193.33.65"
     port = 3306
     port = 3306
     login = "nodeny"
     login = "nodeny-radius"
     password = "hardpass"
     password = "hardpass"
     radius_db = "nodeny"
     radius_db = "nodeny"


     read_groups = no
     read_groups = no
     #authorize_check_query = "call radcheck('%{User-Name}')"
     authorize_reply_query = "call radreply('%{User-Name}', '%{NAS-IP-Address}')"
    authorize_reply_query = "call radreply('%{User-Name}')"
     accounting {
     accounting {
         reference = "%{tolower:type.%{Acct-Status-Type}.query}"
         reference = "%{tolower:type.%{Acct-Status-Type}.query}"
         type {
         type {
             start {
             start {
                 query = "call radupdate('%{reply:Callback-Number}','%{Framed-IP-Address}',\
                 query = "call radupdate(\
                        'user=%{Calling-Station-Id};nas=%{NAS-IP-Address};ses=%{Acct-Session-Id}')"
                  '%{ERX-Dhcp-Mac-Addr}', '%{Framed-IP-Address}',\
                  '%{NAS-IP-Address}', '%{Acct-Session-Id}', '',\
                  (%{%{Acct-Input-Gigawords}:-0} * POWER(2, 32)) + %{%{Acct-Input-Octets}:-0},\
                  (%{%{Acct-Output-Gigawords}:-0} * POWER(2, 32)) + %{%{Acct-Output-Octets}:-0})"
             }
             }
             interim-update {
             interim-update {
Рядок 145: Рядок 194:
             }
             }
             stop {
             stop {
                 query = "call radstop('%{reply:Callback-Number}', '%{Framed-IP-Address}')"
                 query = "call radstop('%{Framed-IP-Address}', '%{NAS-IP-Address}')"
             }
             }
         }
         }
     }
     }
     post-auth {
     post-auth {
         query = "call radupdate('%{reply:Callback-Number}','%{reply:Framed-IP-Address}',\
         query = "call radupdate(\
                 'user=%{Calling-Station-Id};nas=%{NAS-IP-Address};ses=%{Acct-Session-Id}')"
                '%{ERX-Dhcp-Mac-Addr}', '%{reply:Framed-IP-Address}',\
                 '%{NAS-IP-Address}', '%{Acct-Session-Id}', ';first_response=1',\
                0, 0)"
    }
    pool {
      start = 5
      min = 5
      max = 130
      spare = 30
      uses = 0
      retry_delay = 30
      lifetime = 0
      idle_timeout = 60
     }
     }
}
}
</pre>
</pre>
==Логика radreply==
В БД у свичей есть параметры:
* svlan - vlan свича
* cvlan - vlan, с которого начинается отсчет клиентских vlan. Клиентский vlan = cvlan + № порта
Например, если клиент подключен к порту 5 свича с cvlan=3000, то у клиента vlan будет 3005
На Radius UserName приходит в закодированном виде: lesnoy|e89a.8f2d.b9f3|ps50:3571-3019
Здесь:
* lesnoy - имя BRAS
* e89a.8f2d.b9f3 - мак клиента
* ps50:3571-3019 - параметры подключения, могут иметь формат:
** aaa:bbb-ccc (bbb - vlan свича,  ccc - vlan клиента)
** aaa:bbb (bbb - vlan свича)
Алгоритм:
* В зависимости от формата подключения:
** Если aaa:bbb-ccc, то из базы выбираем подключение где: BRAS.name=lesnoy, BRAS.svlan=bbb, USER.port=ccc-BRAS.cvlan
** Если aaa:bbb, то из базы выбираем подключение где: USER.mac=e89a.8f2d.b9f3
* Если подключение не найдено, то
** Находим в базе NAS (группа 6) по ip из атрибута NAS-IP-Address
** В данных NAS в параметре _ip_pool содержится номер пула. Далее мы обрабатываем записи в ip_pool только с типом = 'pool_№пула'
** Сначала смотрим выдавался ли ранее данному клиенту какой либо ip. И если выдавался, выдадим его. Как определяем какой ip выдавался:
*** в поле guest хранится полный UserName клиента
*** поле release указывает когда ip можно освободить
** Если не выдавался, то выдаем рандомный ip из пула
** Устанавливаем release = now + 1 час
** return svc-guest-ipoe(svc-filter-notregistered)
* Если не подвязан ip, то return svc-guest-ipoe(svc-filter-disable)
* В ответ добавляем строку: Framed-IP-Address = хх.хх.хх.хх
* Если не подвязана услуга типа "интернет", то return svc-guest-ipoe(svc-filter-disable)
* Если баланс меньше 0, то return svc-guest-ipoe(svc-filter-nomoney)
* В ответ добавляем атрибуты из услуги


==Mysql процедуры==
==Mysql процедуры==
Рядок 171: Рядок 269:
     ))$$
     ))$$
DELIMITER ;
DELIMITER ;
</pre>


===radreply===
<pre>
DROP PROCEDURE IF EXISTS `radreply`;
DROP PROCEDURE IF EXISTS `radreply`;
DELIMITER $$
DELIMITER $$
CREATE PROCEDURE `radreply`(IN encoded_user_name VARCHAR(128))
CREATE PROCEDURE `radreply`(IN encoded_user_name VARCHAR(60), IN nas_ip VARCHAR(15))
BEGIN
BEGIN
     DECLARE nas_name VARCHAR(32);
     DECLARE nas_name VARCHAR(32);
     DECLARE usr_mac VARCHAR(12);
     DECLARE usr_mac VARCHAR(12);
     DECLARE vlans VARCHAR(64);
     DECLARE vlans VARCHAR(50);
     DECLARE svlan VARCHAR(6);
     DECLARE svlan VARCHAR(6);
     DECLARE cvlan VARCHAR(6);
     DECLARE cvlan VARCHAR(6);
     DECLARE usr_id INT;
     DECLARE usr_id INT;
     DECLARE usr_ip VARCHAR(15) DEFAULT NULL;
     DECLARE usr_ip VARCHAR(15) DEFAULT NULL;
    DECLARE usr_state VARCHAR(10);
    DECLARE usr_balance FLOAT(10,2);
     DECLARE add_attr MEDIUMTEXT;
     DECLARE add_attr MEDIUMTEXT;
     DECLARE line MEDIUMTEXT;
     DECLARE line MEDIUMTEXT;
     DECLARE i INT DEFAULT 1;
     DECLARE i INT DEFAULT 1;
    DECLARE ip_pool INT;


     SELECT strSplit(encoded_user_name, '=7C', 1) INTO nas_name;
     SELECT strSplit(encoded_user_name, '=7C', 1) INTO nas_name;
Рядок 195: Рядок 299:


     IF( LENGTH(cvlan) > 0 ) THEN
     IF( LENGTH(cvlan) > 0 ) THEN
         SELECT m.uid INTO usr_id
         SELECT m.uid, INET_NTOA(m.ip) INTO usr_id, usr_ip
           FROM mac_uid m
           FROM mac_uid m
           JOIN data0 d ON m.device_mac=d._mac
           JOIN data0 d ON m.device_mac=d._mac
           WHERE d._bras=nas_name AND d._svlan=svlan AND m.device_port=cvlan;
           WHERE d._bras=nas_name
            AND d._svlan=svlan
            AND m.device_port=cvlan-d._cvlan
          LIMIT 1;
     ELSE
     ELSE
         SELECT m.uid INTO usr_id
         SELECT m.uid, INET_NTOA(m.ip) INTO usr_id, usr_ip
           FROM mac_uid m
           FROM mac_uid m
          JOIN data0 d ON m.device_mac=d._mac
           WHERE m.mac=usr_mac;
           WHERE d._bras=nas_name AND m.device_port=svlan AND m.mac=usr_mac;
     END IF;
     END IF;


     IF( usr_id IS NULL ) THEN
    INSERT INTO rad_log
         SELECT NULL,encoded_user_name,'ERX-Service-Activate:1','svc-guest-ipoe(svc-filter-notregistered)','+=';
      SET username=encoded_user_name,
          nas_name=nas_name,
          svlan=svlan,
          cvlan=cvlan,
          user_id=usr_id,
          user_ip=usr_ip;
 
    SELECT NULL,encoded_user_name,'ERX-Virtual-Router-Name','"brs"','=';
 
     IF( usr_id IS NULL OR usr_id=0 ) THEN
 
      SELECT d._ip_pool INTO ip_pool
        FROM data0 d JOIN users u ON d.uid=u.id
        WHERE u.grp=6
          AND d._nas_ip=nas_ip
         LIMIT 1;
 
      UPDATE ip_pool
        SET `release`=UNIX_TIMESTAMP()+3600
        WHERE type=CONCAT('pool_', ip_pool)
          AND guest=encoded_user_name
        LIMIT 1;
 
      IF( ROW_COUNT() < 1 ) THEN
        UPDATE ip_pool SET guest='', `release`=0
          WHERE `release`>0 AND `release`<UNIX_TIMESTAMP();
 
        UPDATE ip_pool
          SET `release`=UNIX_TIMESTAMP()+3600,
              guest=encoded_user_name
          WHERE type=CONCAT('pool_', ip_pool)
            AND guest=''
        LIMIT 1;
      END IF;
 
      SELECT INET_NTOA(ip) INTO usr_ip
        FROM ip_pool
        WHERE type=CONCAT('pool_', ip_pool)
          AND guest=encoded_user_name
      LIMIT 1;
 
      SELECT NULL,encoded_user_name,'Framed-IP-Address',usr_ip,'=';
      SELECT NULL,encoded_user_name,'ERX-Service-Activate:1',
        'svc-guest-ipoe-new(svc-filter-notregistered)','+=';
 
     ELSE
     ELSE
       SELECT get_ip(usr_id) INTO usr_ip;
 
       SELECT NULL,encoded_user_name,'Callback-Number',usr_id,'=';
      SELECT INET_NTOA(ip) INTO usr_ip FROM ip_pool
          WHERE uid=usr_id AND type='static'
          ORDER BY INET_NTOA(ip)=usr_ip DESC LIMIT 1;
 
      IF( usr_ip IS NOT NULL ) THEN
          SELECT NULL,encoded_user_name,'Framed-IP-Address',usr_ip,'=';
      END IF;
 
       SELECT radius_attr INTO add_attr FROM users_services
       SELECT radius_attr INTO add_attr FROM users_services
         WHERE uid=usr_id AND tags LIKE '%,inet,%' LIMIT 1;
         WHERE uid=usr_id AND tags LIKE '%,inet,%' LIMIT 1;
      SELECT balance, state INTO usr_balance, usr_state FROM users
        WHERE id=usr_id LIMIT 1;


       SELECT NULL,encoded_user_name,'Callback-Number',usr_id,'=';
       IF( add_attr IS NULL OR usr_ip IS NULL ) THEN
       SELECT NULL,encoded_user_name,'Framed-IP-Address',usr_ip,'=';
          SELECT NULL,encoded_user_name,'ERX-Service-Activate:1','svc-guest-ipoe-new(svc-filter-disable)','+=';
      ELSEIF( usr_state = 'off' AND usr_balance < 0 ) THEN
          SELECT NULL,encoded_user_name,'ERX-Service-Activate:1','svc-guest-ipoe-new(svc-filter-nomoney)','+=';
       ELSEIF( usr_state = 'off' ) THEN
          SELECT NULL,encoded_user_name,'ERX-Service-Activate:1','svc-guest-ipoe-new(svc-filter-disable)','+=';
      ELSE
          attr_loop: WHILE TRUE DO
            SELECT strSplit(add_attr, '\n', i) INTO line;
            IF LENGTH(line) = 0 OR i > 20 THEN LEAVE attr_loop; END IF;
            IF line LIKE '%+=%' THEN
                SELECT NULL,encoded_user_name,strSplit(line, '+=', 1),strSplit(line, '+=', 2),'+=';
            ELSEIF line LIKE '%=%' THEN
                SELECT NULL,encoded_user_name,strSplit(line, '=', 1),strSplit(line, '=', 2),'=';
            END IF;
            SET i = i + 1;
          END WHILE;
      END IF;


      attr_loop: WHILE TRUE DO
        SELECT strSplit(add_attr, '\n', i) INTO line;
        IF LENGTH(line) = 0 OR i > 20 THEN LEAVE attr_loop; END IF;
        IF line LIKE '%+=%' THEN
            SELECT NULL,encoded_user_name,strSplit(line, '+=', 1),strSplit(line, '+=', 2),'+=';
        ELSEIF line LIKE '%=%' THEN
            SELECT NULL,encoded_user_name,strSplit(line, '=', 1),strSplit(line, '=', 2),'=';
        END IF;
        SET i = i + 1;
      END WHILE;
     END IF;
     END IF;
END$$
END$$
DELIMITER ;
DELIMITER ;


</pre>
===radupdate===
<pre>
DROP PROCEDURE IF EXISTS `radupdate`;
DROP PROCEDURE IF EXISTS `radupdate`;
DELIMITER $$
DELIMITER $$
CREATE PROCEDURE `radupdate`(
CREATE PROCEDURE `radupdate`(
     IN usr_id VARCHAR(20), IN ipa VARCHAR(16), IN properties VARCHAR(255))
     IN usr_mac VARCHAR(20),
    IN usr_ip VARCHAR(16),
    IN nas_ip VARCHAR(16),
    IN ses VARCHAR(64),
    IN properties VARCHAR(64),
    IN trafin BIGINT(20),
    IN trafout BIGINT(20)
)
BEGIN
    DECLARE user_id INT UNSIGNED;
    SELECT uid INTO user_id FROM ip_pool WHERE ip=INET_ATON(usr_ip);
    IF( user_id IS NOT NULL AND user_id > 0 ) THEN
      INSERT INTO ses_traf
        SET ses_id=ses, traf_in=trafin, traf_out=trafout, time=UNIX_TIMESTAMP(), uid=user_id;
    END IF;
    CALL set_auth(usr_ip, CONCAT(
        'nas=', nas_ip, ';ses=', ses, properties, ';mod=dhcp;user=', REPLACE(usr_mac, '.', '')
    ));
    UPDATE mac_uid SET time=UNIX_TIMESTAMP() WHERE ip=INET_ATON(usr_ip) LIMIT 1;
    UPDATE ip_pool
        SET `release`=UNIX_TIMESTAMP()+3600,
            last_session=CONCAT(nas_ip, ' ', ses)
        WHERE ip=INET_ATON(usr_ip) AND type LIKE 'pool_%';
END$$
DELIMITER ;
</pre>
 
===radstop===
Авторизация завершается только если radstop послал bras, который последним делал аккаунтинг. Это ситуация от заказчика: bras1 становится недоступным, клиент переключается на bras2, bras1 возвращается и посылает radstop. Этот radstop мы игнорируем т.к. клиент подключен через bras1
 
Обратить внимание, что ip bras-а сохраняем в поле properties первым параметром, чтобы работал LIKE CONCAT('nas=', nas_ipa, '%'). Поэтому:
 
# mods-enabled/sql в параметре 'nas=%{NAS-IP-Address};...;...' nas на первом месте
# в radupdate переменная properties идет на первом месте при вызове set_auth
 
<pre>
DROP PROCEDURE IF EXISTS `radstop`;
DELIMITER $$
CREATE PROCEDURE `radstop`(IN user_ipa VARCHAR(16), IN nas_ipa VARCHAR(16))
BEGIN
BEGIN
     CALL set_auth(ipa, CONCAT('mod=dhcp;', REPLACE(properties,';','')));
     DECLARE auth_prop VARCHAR(255);
    UPDATE mac_uid SET time=UNIX_TIMESTAMP() WHERE ip=INET_ATON(ipa) LIMIT 1;
    DECLARE user_id INT UNSIGNED;
    DECLARE start_auth INT UNSIGNED;
    SELECT a.properties, a.start, i.uid INTO auth_prop, start_auth, user_id
        FROM ip_pool i
        JOIN auth_now a ON i.ip = INET_ATON(user_ipa)
        JOIN users u ON i.uid=u.id
        WHERE a.ip=user_ipa;
    IF( user_id<>0 AND auth_prop IS NOT NULL AND auth_prop LIKE CONCAT('nas=', nas_ipa, '%') ) THEN
        INSERT INTO auth_log (uid, ip, start, end, properties) VALUES
            (user_id, INET_ATON(user_ipa), start_auth, UNIX_TIMESTAMP(), auth_prop);
        DELETE FROM auth_now WHERE ip=user_ipa LIMIT 1;
    END IF;
END$$
END$$
DELIMITER ;
DELIMITER ;
Рядок 250: Рядок 471:
В соседней консоли тестовый запрос:
В соседней консоли тестовый запрос:
<source lang="bash">
<source lang="bash">
radtest "lesnoy|e89a.8f2d.b9f3|ps50:3571-3019" '' 127.0.0.1 0 hardpass5
echo "User-Name=\"test|0855.31fa.d608|ps20:1002-1284\",User-Password=\"\",Acct-Session-Id=\"1\",NAS-IP-Address=\"172.16.66.249\"" | radclient -x localhost:1812 auth hardpass5
</source>
</source>


В ответ должны получить следующее:
В ответ должны получить следующее:
<pre>
<pre>
    Callback-Number = "4"
Callback-Number = "14192"
    Framed-IP-Address = 10.0.0.5
Framed-IP-Address = 100.66.56.104
    ERX-Service-Activate:1 = "svc-global-ipoe(128000, 128000)"
ERX-Virtual-Router-Name = "brs"
ERX-Service-Activate:1 = "svc-global-ipoe(32000000,32000000)"
</pre>
</pre>


В админ интерфейсе в данных абонента напротив его ip должен появиться зеленый ключик.
В админ интерфейсе в данных абонента напротив его ip должен появиться зеленый ключик.
==Разное==
В /etc/mysql/mariadb.conf.d/50-server.cnf:
<pre>
max_connections        = 250
</pre>
Было 150. Однако, необходимость под вопросом
==як працює регістрація ip==
1) перевіряється чи існує ip в пулі ip (таблиця ip_pool). Якщо ні, значить ip був виданий некоректно (хтось зломав логіку білінгу, доналаштувавши на свій погляд) В цьому випадку клієнту виводиться месадж: 'Регистрация невозможна из-за неправильной настройки вашего подключения (неизвестный ip)'
2) якщо тип ip static - це означає, що клієнту був виданий ip не з пулу дінамічних, а зі статичних. Виводиться месадж 'Вероятно вы уже прошли регистрацию (static ip type)'
3) якщо тип НЕ починається з префіксу pool (а ми робили цей префікс чиспо під андернет), то виводиться месадж 'Регистрация невозможна, обратитесь к администрации (incorrect ip type)'
4) у ip в пулі є параметр release, який означає, коли буде ця дінамічна адреса вважатися вільною. При отриманні ip, release встановлюється на деЯкий час в майбутньому. Якщо release переступив поточний час, то клієнт отримує месадж 'Прошло много времени с момента подключения, необходимо перезагрузить компьютер'
5) В полі guest запису в пулі ip зберігаються параметри, при яких клієнт отримав ip. В випадку андернета це UserName в радіус-пакеті. Нагадаю формат:
lesnoy|e89a.8f2d.b9f3|ps50:3571-3019
Якщо структура не відповідає, то месадж 'Регистрация невозможна, обратитесь к администрации (incorrect connection data)'
6) Шукається свіч по параметрам підключення:
- свіч повинен бути у групі 2
- в допаних поле _bras='lesnoy'
- _svlan, _cvlan
Ще в налаштуваннях коректно повино бути вказаним $cfg::device_mac_field - поле, в якому зберігається мак свіча
Свіч не знайдений - месадж 'Регистрация невозможна, обратитесь к администрации (switch not found)'
7) якщо в таблиці mac_uid існує запис з device_mac=мак свіча AND device_port=cvlan, то у цій зв'язки встановлюється id абонета, інакше така зв'язка створюється
в таблицю  pod_queue вставляється запис що треба відрубити юзера з такою сесією
месадж 'Ваш компьютер зарегистрирован'

Поточна версія на 15:26, 29 вересня 2024

Добавить в пул ip поле для гостевых подключений

alter table ip_pool add guest varchar(60) not null default '';
alter table ip_pool add key (guest);
alter table ip_pool add last_session varchar(64) not null default '';
alter table ip_pool change `type` `type` enum(
   'static','dynamic','reserved',
   'pool_0', 'pool_1', 'pool_2', 'pool_3', 'pool_4',
   'pool_5', 'pool_6', 'pool_7', 'pool_8', 'pool_9'
) NOT NULL;


CREATE TABLE `pod_queue` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `usr_ip` varchar(16) NOT NULL DEFAULT '',
  `nas_ip` varchar(16) NOT NULL DEFAULT '',
  `session` varchar(128) NOT NULL DEFAULT '',
  `created_at` int(10) unsigned NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `created_at` (`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=1;

CREATE TABLE `rad_log` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created_at` DATETIME DEFAULT NOW(),
  `username` varchar(127) NULL,
  `nas_name` varchar(32) NULL,
  `svlan` varchar(32) NULL,
  `cvlan` varchar(32) NULL,
  `user_id` int(11) NULL,
  `user_ip` varchar(15) NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

Настройка NoDeny

Установить модуль radius атрибутов

Создать тариф 1Мбит, параметр radius установить в:

ERX-Service-Activate:1+= "svc-global-ipoe(128000, 128000)"
ERX-Virtual-Router-Name = "brs"

Создать группу

  • имя: switch
  • галки только на:
    • К учетным записям подключаются ip, имеют трафик
    • Учетные записи имеют сущность «Порты»

Дополнительные поля

  • Имя поля: BRAS
  • Имя поля в бд: _bras
  • Тип поля: выпадающий список
  • Галки по усмотрению
  • Тип объекта: bras
  • Группы: switch

  • Имя поля: Service VLAN
  • Имя поля в бд: _svlan
  • Тип поля: целое положительное
  • Группы: switch

  • Имя поля: Client VLAN
  • Имя поля в бд: _cvlan
  • Тип поля: целое положительное
  • Группы: switch

  • Имя поля: MAC адрес
  • Имя поля в бд: _mac
  • Тип поля: мак
  • Группы: switch

В Настроках-Dhcp/оборудование

Имя дополнительного поля, в котором хранится мак-адрес свича установить в _mac

Тестовые данные

Создаем switch:

  • MAC адрес: 001122334455
  • BRAS: lesnoy
  • SVLAN: 3571

Создаем клиента. Данные неважны. Добавляем услугу: 1 Мбит Добавляем ip: 10.0.0.5

Добавляем подключение:

  • мак: e89a8f2db9f3
  • Мак или идентификатор устройства - выбираем созданный switch
  • Порт устройства или vlan: 3019
  • ip: 10.0.0.5

Установка Radius

apt install freeradius freeradius-mysql
rm /etc/freeradius/3.0/sites-enabled/default
cp /usr/local/nodeny/etc/raddb/clients.conf /etc/freeradius/3.0/

Конфигурирование

nano /etc/freeradius/3.0/sites-enabled/nodeny

Вставляем следующий текст:

server default {
    listen {
        type = auth
        ipaddr = *
        port = 1812
    }
    listen {
        type = acct
        ipaddr = *
        port = 0
    }
    authorize {
        sql
        update control {
          Auth-Type := Accept
        }
    }
    authenticate {
    }
    preacct {
        acct_unique
        preprocess
    }
    accounting {
        sql
        ok
    }
    session {
        radutmp
        sql
    }
    post-auth {
        sql
        ok
    }
    Post-Auth-Type ACCEPT {
        sql
    }
}

Sql конфиг

В консоли:

nano /etc/freeradius/3.0/mods-enabled/sql

Вставляем следующий текст:

sql {
    driver = "rlm_sql_mysql"
    mysql {
        warnings = auto
    }
    server = "91.193.33.65"
    port = 3306
    login = "nodeny-radius"
    password = "hardpass"
    radius_db = "nodeny"

    read_groups = no
    authorize_reply_query = "call radreply('%{User-Name}', '%{NAS-IP-Address}')"
    accounting {
        reference = "%{tolower:type.%{Acct-Status-Type}.query}"
        type {
            start {
                query = "call radupdate(\
                  '%{ERX-Dhcp-Mac-Addr}', '%{Framed-IP-Address}',\
                  '%{NAS-IP-Address}', '%{Acct-Session-Id}', '',\
                  (%{%{Acct-Input-Gigawords}:-0} * POWER(2, 32)) + %{%{Acct-Input-Octets}:-0},\
                  (%{%{Acct-Output-Gigawords}:-0} * POWER(2, 32)) + %{%{Acct-Output-Octets}:-0})"
            }
            interim-update {
                query = "${..start.query}"
            }
            stop {
                query = "call radstop('%{Framed-IP-Address}', '%{NAS-IP-Address}')"
            }
        }
    }
    post-auth {
        query = "call radupdate(\
                '%{ERX-Dhcp-Mac-Addr}', '%{reply:Framed-IP-Address}',\
                '%{NAS-IP-Address}', '%{Acct-Session-Id}', ';first_response=1',\
                0, 0)"
    }
    pool {
      start = 5
      min = 5
      max = 130
      spare = 30
      uses = 0
      retry_delay = 30
      lifetime = 0
      idle_timeout = 60
    }
}

Логика radreply

В БД у свичей есть параметры:

  • svlan - vlan свича
  • cvlan - vlan, с которого начинается отсчет клиентских vlan. Клиентский vlan = cvlan + № порта

Например, если клиент подключен к порту 5 свича с cvlan=3000, то у клиента vlan будет 3005


На Radius UserName приходит в закодированном виде: lesnoy|e89a.8f2d.b9f3|ps50:3571-3019

Здесь:

  • lesnoy - имя BRAS
  • e89a.8f2d.b9f3 - мак клиента
  • ps50:3571-3019 - параметры подключения, могут иметь формат:
    • aaa:bbb-ccc (bbb - vlan свича, ccc - vlan клиента)
    • aaa:bbb (bbb - vlan свича)

Алгоритм:

  • В зависимости от формата подключения:
    • Если aaa:bbb-ccc, то из базы выбираем подключение где: BRAS.name=lesnoy, BRAS.svlan=bbb, USER.port=ccc-BRAS.cvlan
    • Если aaa:bbb, то из базы выбираем подключение где: USER.mac=e89a.8f2d.b9f3
  • Если подключение не найдено, то
    • Находим в базе NAS (группа 6) по ip из атрибута NAS-IP-Address
    • В данных NAS в параметре _ip_pool содержится номер пула. Далее мы обрабатываем записи в ip_pool только с типом = 'pool_№пула'
    • Сначала смотрим выдавался ли ранее данному клиенту какой либо ip. И если выдавался, выдадим его. Как определяем какой ip выдавался:
      • в поле guest хранится полный UserName клиента
      • поле release указывает когда ip можно освободить
    • Если не выдавался, то выдаем рандомный ip из пула
    • Устанавливаем release = now + 1 час
    • return svc-guest-ipoe(svc-filter-notregistered)
  • Если не подвязан ip, то return svc-guest-ipoe(svc-filter-disable)
  • В ответ добавляем строку: Framed-IP-Address = хх.хх.хх.хх
  • Если не подвязана услуга типа "интернет", то return svc-guest-ipoe(svc-filter-disable)
  • Если баланс меньше 0, то return svc-guest-ipoe(svc-filter-nomoney)
  • В ответ добавляем атрибуты из услуги

Mysql процедуры

ALTER DATABASE nodeny CHARACTER SET utf8 COLLATE utf8_general_ci;
DELIMITER $$
CREATE FUNCTION `strSplit`(x MEDIUMTEXT, delim MEDIUMTEXT, pos int) RETURNS mediumtext CHARSET utf8
    DETERMINISTIC
RETURN 
    TRIM(BOTH '\r' FROM TRIM(
        REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos), LENGTH(SUBSTRING_INDEX(x, delim, pos - 1)) + 1), delim, '')
    ))$$
DELIMITER ;

radreply

DROP PROCEDURE IF EXISTS `radreply`;
DELIMITER $$
CREATE PROCEDURE `radreply`(IN encoded_user_name VARCHAR(60), IN nas_ip VARCHAR(15))
BEGIN
    DECLARE nas_name VARCHAR(32);
    DECLARE usr_mac VARCHAR(12);
    DECLARE vlans VARCHAR(50);
    DECLARE svlan VARCHAR(6);
    DECLARE cvlan VARCHAR(6);
    DECLARE usr_id INT;
    DECLARE usr_ip VARCHAR(15) DEFAULT NULL;
    DECLARE usr_state VARCHAR(10);
    DECLARE usr_balance FLOAT(10,2);
    DECLARE add_attr MEDIUMTEXT;
    DECLARE line MEDIUMTEXT;
    DECLARE i INT DEFAULT 1;
    DECLARE ip_pool INT;

    SELECT strSplit(encoded_user_name, '=7C', 1) INTO nas_name;
    SELECT REPLACE(strSplit(encoded_user_name, '=7C', 2), '.', '') INTO usr_mac;
    SELECT strSplit(encoded_user_name, '=7C', 3) INTO vlans;
    SELECT strSplit(vlans, ':', 2) INTO vlans;
    SELECT strSplit(vlans, '-', 1) INTO svlan;
    SELECT strSplit(vlans, '-', 2) INTO cvlan;

    IF( LENGTH(cvlan) > 0 ) THEN
        SELECT m.uid, INET_NTOA(m.ip) INTO usr_id, usr_ip
          FROM mac_uid m
          JOIN data0 d ON m.device_mac=d._mac
          WHERE d._bras=nas_name
            AND d._svlan=svlan
            AND m.device_port=cvlan-d._cvlan
          LIMIT 1;
    ELSE
        SELECT m.uid, INET_NTOA(m.ip) INTO usr_id, usr_ip
          FROM mac_uid m
          WHERE m.mac=usr_mac;
    END IF;

    INSERT INTO rad_log
      SET username=encoded_user_name,
          nas_name=nas_name,
          svlan=svlan,
          cvlan=cvlan,
          user_id=usr_id,
          user_ip=usr_ip;

    SELECT NULL,encoded_user_name,'ERX-Virtual-Router-Name','"brs"','=';

    IF( usr_id IS NULL OR usr_id=0 ) THEN

      SELECT d._ip_pool INTO ip_pool
        FROM data0 d JOIN users u ON d.uid=u.id
        WHERE u.grp=6
          AND d._nas_ip=nas_ip
        LIMIT 1;

      UPDATE ip_pool
        SET `release`=UNIX_TIMESTAMP()+3600
        WHERE type=CONCAT('pool_', ip_pool) 
          AND guest=encoded_user_name
        LIMIT 1;

      IF( ROW_COUNT() < 1 ) THEN
        UPDATE ip_pool SET guest='', `release`=0
          WHERE `release`>0 AND `release`<UNIX_TIMESTAMP();

        UPDATE ip_pool
          SET `release`=UNIX_TIMESTAMP()+3600,
              guest=encoded_user_name
          WHERE type=CONCAT('pool_', ip_pool)
            AND guest=''
        LIMIT 1;
      END IF;

      SELECT INET_NTOA(ip) INTO usr_ip
        FROM ip_pool
        WHERE type=CONCAT('pool_', ip_pool)
          AND guest=encoded_user_name
      LIMIT 1;

      SELECT NULL,encoded_user_name,'Framed-IP-Address',usr_ip,'=';
      SELECT NULL,encoded_user_name,'ERX-Service-Activate:1',
        'svc-guest-ipoe-new(svc-filter-notregistered)','+=';

    ELSE

      SELECT NULL,encoded_user_name,'Callback-Number',usr_id,'=';
      SELECT INET_NTOA(ip) INTO usr_ip FROM ip_pool
          WHERE uid=usr_id AND type='static'
          ORDER BY INET_NTOA(ip)=usr_ip DESC LIMIT 1;

      IF( usr_ip IS NOT NULL ) THEN
          SELECT NULL,encoded_user_name,'Framed-IP-Address',usr_ip,'=';
      END IF;

      SELECT radius_attr INTO add_attr FROM users_services
        WHERE uid=usr_id AND tags LIKE '%,inet,%' LIMIT 1;
      SELECT balance, state INTO usr_balance, usr_state FROM users
        WHERE id=usr_id LIMIT 1;

      IF( add_attr IS NULL OR usr_ip IS NULL ) THEN
          SELECT NULL,encoded_user_name,'ERX-Service-Activate:1','svc-guest-ipoe-new(svc-filter-disable)','+=';
      ELSEIF( usr_state = 'off' AND usr_balance < 0 ) THEN
          SELECT NULL,encoded_user_name,'ERX-Service-Activate:1','svc-guest-ipoe-new(svc-filter-nomoney)','+=';
      ELSEIF( usr_state = 'off' ) THEN
          SELECT NULL,encoded_user_name,'ERX-Service-Activate:1','svc-guest-ipoe-new(svc-filter-disable)','+=';
      ELSE
          attr_loop: WHILE TRUE DO
            SELECT strSplit(add_attr, '\n', i) INTO line;
            IF LENGTH(line) = 0 OR i > 20 THEN LEAVE attr_loop; END IF;
            IF line LIKE '%+=%' THEN
                SELECT NULL,encoded_user_name,strSplit(line, '+=', 1),strSplit(line, '+=', 2),'+=';
            ELSEIF line LIKE '%=%' THEN
                SELECT NULL,encoded_user_name,strSplit(line, '=', 1),strSplit(line, '=', 2),'=';
            END IF;
            SET i = i + 1;
          END WHILE;
      END IF;

    END IF;
END$$
DELIMITER ;


radupdate

DROP PROCEDURE IF EXISTS `radupdate`;
DELIMITER $$
CREATE PROCEDURE `radupdate`(
    IN usr_mac VARCHAR(20),
    IN usr_ip VARCHAR(16),
    IN nas_ip VARCHAR(16),
    IN ses VARCHAR(64),
    IN properties VARCHAR(64),
    IN trafin BIGINT(20),
    IN trafout BIGINT(20)
)
BEGIN
    DECLARE user_id INT UNSIGNED;
    SELECT uid INTO user_id FROM ip_pool WHERE ip=INET_ATON(usr_ip);
    IF( user_id IS NOT NULL AND user_id > 0 ) THEN
       INSERT INTO ses_traf
         SET ses_id=ses, traf_in=trafin, traf_out=trafout, time=UNIX_TIMESTAMP(), uid=user_id;
    END IF;
    CALL set_auth(usr_ip, CONCAT(
        'nas=', nas_ip, ';ses=', ses, properties, ';mod=dhcp;user=', REPLACE(usr_mac, '.', '')
    ));
    UPDATE mac_uid SET time=UNIX_TIMESTAMP() WHERE ip=INET_ATON(usr_ip) LIMIT 1;
    UPDATE ip_pool
        SET `release`=UNIX_TIMESTAMP()+3600,
            last_session=CONCAT(nas_ip, ' ', ses)
        WHERE ip=INET_ATON(usr_ip) AND type LIKE 'pool_%';
END$$
DELIMITER ;

radstop

Авторизация завершается только если radstop послал bras, который последним делал аккаунтинг. Это ситуация от заказчика: bras1 становится недоступным, клиент переключается на bras2, bras1 возвращается и посылает radstop. Этот radstop мы игнорируем т.к. клиент подключен через bras1

Обратить внимание, что ip bras-а сохраняем в поле properties первым параметром, чтобы работал LIKE CONCAT('nas=', nas_ipa, '%'). Поэтому:

  1. mods-enabled/sql в параметре 'nas=%{NAS-IP-Address};...;...' nas на первом месте
  2. в radupdate переменная properties идет на первом месте при вызове set_auth
DROP PROCEDURE IF EXISTS `radstop`;
DELIMITER $$
CREATE PROCEDURE `radstop`(IN user_ipa VARCHAR(16), IN nas_ipa VARCHAR(16))
BEGIN
    DECLARE auth_prop VARCHAR(255);
    DECLARE user_id INT UNSIGNED;
    DECLARE start_auth INT UNSIGNED;
    SELECT a.properties, a.start, i.uid INTO auth_prop, start_auth, user_id
        FROM ip_pool i
        JOIN auth_now a ON i.ip = INET_ATON(user_ipa)
        JOIN users u ON i.uid=u.id
        WHERE a.ip=user_ipa;
    IF( user_id<>0 AND auth_prop IS NOT NULL AND auth_prop LIKE CONCAT('nas=', nas_ipa, '%') ) THEN
        INSERT INTO auth_log (uid, ip, start, end, properties) VALUES
            (user_id, INET_ATON(user_ipa), start_auth, UNIX_TIMESTAMP(), auth_prop);
        DELETE FROM auth_now WHERE ip=user_ipa LIMIT 1;
    END IF;
END$$
DELIMITER ;

Тестирование

Запускаем радиус в режиме вывода в консоль:

freeradius -X

В соседней консоли тестовый запрос:

echo "User-Name=\"test|0855.31fa.d608|ps20:1002-1284\",User-Password=\"\",Acct-Session-Id=\"1\",NAS-IP-Address=\"172.16.66.249\"" | radclient -x localhost:1812 auth hardpass5

В ответ должны получить следующее:

	Callback-Number = "14192"
	Framed-IP-Address = 100.66.56.104
	ERX-Virtual-Router-Name = "brs"
	ERX-Service-Activate:1 = "svc-global-ipoe(32000000,32000000)"

В админ интерфейсе в данных абонента напротив его ip должен появиться зеленый ключик.

Разное

В /etc/mysql/mariadb.conf.d/50-server.cnf:

max_connections        = 250

Было 150. Однако, необходимость под вопросом

як працює регістрація ip

1) перевіряється чи існує ip в пулі ip (таблиця ip_pool). Якщо ні, значить ip був виданий некоректно (хтось зломав логіку білінгу, доналаштувавши на свій погляд) В цьому випадку клієнту виводиться месадж: 'Регистрация невозможна из-за неправильной настройки вашего подключения (неизвестный ip)'

2) якщо тип ip static - це означає, що клієнту був виданий ip не з пулу дінамічних, а зі статичних. Виводиться месадж 'Вероятно вы уже прошли регистрацию (static ip type)'

3) якщо тип НЕ починається з префіксу pool (а ми робили цей префікс чиспо під андернет), то виводиться месадж 'Регистрация невозможна, обратитесь к администрации (incorrect ip type)'

4) у ip в пулі є параметр release, який означає, коли буде ця дінамічна адреса вважатися вільною. При отриманні ip, release встановлюється на деЯкий час в майбутньому. Якщо release переступив поточний час, то клієнт отримує месадж 'Прошло много времени с момента подключения, необходимо перезагрузить компьютер'

5) В полі guest запису в пулі ip зберігаються параметри, при яких клієнт отримав ip. В випадку андернета це UserName в радіус-пакеті. Нагадаю формат: lesnoy|e89a.8f2d.b9f3|ps50:3571-3019 Якщо структура не відповідає, то месадж 'Регистрация невозможна, обратитесь к администрации (incorrect connection data)'

6) Шукається свіч по параметрам підключення: - свіч повинен бути у групі 2 - в допаних поле _bras='lesnoy' - _svlan, _cvlan

Ще в налаштуваннях коректно повино бути вказаним $cfg::device_mac_field - поле, в якому зберігається мак свіча

Свіч не знайдений - месадж 'Регистрация невозможна, обратитесь к администрации (switch not found)'

7) якщо в таблиці mac_uid існує запис з device_mac=мак свіча AND device_port=cvlan, то у цій зв'язки встановлюється id абонета, інакше така зв'язка створюється в таблицю pod_queue вставляється запис що треба відрубити юзера з такою сесією

месадж 'Ваш компьютер зарегистрирован'