Undernet Radius

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

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

alter table ip_pool add guest varchar(60) not null default '';
alter table ip_pool add key (guest);

Настройка 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 = "localhost"
    port = 3306
    login = "nodeny"
    password = "xxx"
    radius_db = "nodeny"

    read_groups = no
    authorize_reply_query = "call radreply('%{User-Name}')"
    accounting {
        reference = "%{tolower:type.%{Acct-Status-Type}.query}"
        type {
            start {
                query = "call radupdate('%{ERX-Dhcp-Mac-Addr}','%{Framed-IP-Address}',\
                        'nas=%{NAS-IP-Address};ses=%{Acct-Session-Id}')"
            }
            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=%{NAS-IP-Address};ses=%{Acct-Session-Id};first_response=1')"
    }
    pool {
      start = 5
      min = 5
      max = 130
      spare = 30
      uses = 0
      retry_delay = 30
      lifetime = 0
      idle_timeout = 60
    }
}

Логика radreply

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

Здесь:

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

Алгоритм:

  • В зависимости от формата подключения:
    • Если aaa:bbb-ccc, то из базы выбираем подключение где: BRAS.name=lesnoy, BRAS.svlan=bbb, USER.port=ccc-BRAS.cvlan
    • Если aaa:bbb, то из базы выбираем подключение где: USER.mac=e89a.8f2d.b9f3
  • Если подключение не найдено, то 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))
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;

    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;

    IF( usr_id IS NULL ) THEN
        UPDATE ip_pool
           SET `release`=UNIX_TIMESTAMP()+3600
           WHERE guest=encoded_user_name LIMIT 1;
        IF( ROW_COUNT() < 1 ) THEN
           UPDATE ip_pool
               SET `release`=UNIX_TIMESTAMP()+3600, guest=encoded_user_name
               WHERE type='reserved' AND guest='' LIMIT 1;
        END IF;
        SELECT INET_NTOA(ip) INTO usr_ip
           FROM ip_pool
           WHERE 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(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 NULL,encoded_user_name,'ERX-Virtual-Router-Name','"brs"','=';

      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(svc-filter-disable)','+=';
      ELSEIF( usr_state = 'off' AND usr_balance < 0 ) THEN
          SELECT NULL,encoded_user_name,'ERX-Service-Activate:1','svc-guest-ipoe(svc-filter-nomoney)','+=';
      ELSEIF( usr_state = 'off' ) THEN
          SELECT NULL,encoded_user_name,'ERX-Service-Activate:1','svc-guest-ipoe(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 ipa VARCHAR(16), IN properties VARCHAR(255))
BEGIN
    CALL set_auth(ipa, CONCAT(properties, ';mod=dhcp;user=', REPLACE(usr_mac, '.', '')));
    UPDATE mac_uid SET time=UNIX_TIMESTAMP() WHERE ip=INET_ATON(ipa) LIMIT 1;
    UPDATE ip_pool SET `release`=UNIX_TIMESTAMP()+3600
        WHERE ip=INET_ATON(ipa) AND type='reserved';
    UPDATE users SET id=id LIMIT 1;
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

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

radtest "lesnoy|e89a.8f2d.b9f3|ps50:3571-3019" '' 127.0.0.1 0 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 должен появиться зеленый ключик.

Чтобы работал COA, надо слать еще сессию:

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

Разное

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

max_connections        = 250

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