Undernet Radius: відмінності між версіями
Sv (обговорення | внесок) |
Sv (обговорення | внесок) мНемає опису редагування |
||
(Не показано 79 проміжних версій цього користувача) | |||
Рядок 1: | Рядок 1: | ||
== | ==Добавить в пул ip поле для гостевых подключений== | ||
<pre> | <pre> | ||
alter table ip_pool add guest varchar( | alter table ip_pool add guest varchar(60) not null default ''; | ||
alter table ip_pool add key (guest); | 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> | </pre> | ||
Рядок 32: | Рядок 61: | ||
* Группы: switch | * Группы: switch | ||
---- | ---- | ||
* Имя поля: | * Имя поля: Service VLAN | ||
* Имя поля в бд: _svlan | * Имя поля в бд: _svlan | ||
* Тип поля: целое положительное | |||
* Группы: switch | |||
---- | |||
* Имя поля: Client VLAN | |||
* Имя поля в бд: _cvlan | |||
* Тип поля: целое положительное | * Тип поля: целое положительное | ||
* Группы: switch | * Группы: switch | ||
Рядок 41: | Рядок 75: | ||
* Тип поля: мак | * Тип поля: мак | ||
* Группы: switch | * Группы: switch | ||
==В Настроках-Dhcp/оборудование== | |||
Имя дополнительного поля, в котором хранится мак-адрес свича установить в _mac | |||
==Тестовые данные== | ==Тестовые данные== | ||
Рядок 104: | Рядок 141: | ||
accounting { | accounting { | ||
sql | sql | ||
ok | |||
} | } | ||
session { | session { | ||
Рядок 111: | Рядок 149: | ||
post-auth { | post-auth { | ||
sql | sql | ||
ok | |||
} | } | ||
Post-Auth-Type ACCEPT { | Post-Auth-Type ACCEPT { | ||
Рядок 133: | Рядок 172: | ||
warnings = auto | warnings = auto | ||
} | } | ||
server = " | 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_reply_query = "call radreply('%{User-Name}', '%{NAS-IP-Address}')" | |||
accounting { | accounting { | ||
reference = "%{tolower:type.%{Acct-Status-Type}.query}" | reference = "%{tolower:type.%{Acct-Status-Type}.query}" | ||
type { | type { | ||
start { | start { | ||
query = "call radupdate('%{ | 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 { | interim-update { | ||
Рядок 153: | Рядок 194: | ||
} | } | ||
stop { | stop { | ||
query = "call radstop('%{ | query = "call radstop('%{Framed-IP-Address}', '%{NAS-IP-Address}')" | ||
} | } | ||
} | } | ||
} | } | ||
post-auth { | post-auth { | ||
query = "call radupdate('%{ | 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 | |||
} | } | ||
} | } | ||
Рядок 166: | Рядок 219: | ||
==Логика radreply== | ==Логика radreply== | ||
UserName приходит в закодированном виде: lesnoy|e89a.8f2d.b9f3|ps50:3571-3019 | В БД у свичей есть параметры: | ||
* svlan - vlan свича | |||
* cvlan - vlan, с которого начинается отсчет клиентских vlan. Клиентский vlan = cvlan + № порта | |||
Например, если клиент подключен к порту 5 свича с cvlan=3000, то у клиента vlan будет 3005 | |||
На Radius UserName приходит в закодированном виде: lesnoy|e89a.8f2d.b9f3|ps50:3571-3019 | |||
Здесь: | Здесь: | ||
Рядок 172: | Рядок 232: | ||
* e89a.8f2d.b9f3 - мак клиента | * e89a.8f2d.b9f3 - мак клиента | ||
* ps50:3571-3019 - параметры подключения, могут иметь формат: | * ps50:3571-3019 - параметры подключения, могут иметь формат: | ||
** aaa:bbb-ccc | ** aaa:bbb-ccc (bbb - vlan свича, ccc - vlan клиента) | ||
** aaa:bbb | ** aaa:bbb (bbb - vlan свича) | ||
Алгоритм: | Алгоритм: | ||
* В зависимости от формата подключения: | * В зависимости от формата подключения: | ||
** Если aaa:bbb-ccc, то из базы выбираем подключение где: BRAS.name=lesnoy, BRAS.svlan=bbb, USER. | ** Если aaa:bbb-ccc, то из базы выбираем подключение где: BRAS.name=lesnoy, BRAS.svlan=bbb, USER.port=ccc-BRAS.cvlan | ||
** Если aaa:bbb, то из базы выбираем подключение где: | ** Если aaa:bbb, то из базы выбираем подключение где: USER.mac=e89a.8f2d.b9f3 | ||
* Если подключение не найдено, то return svc-guest-ipoe(svc-filter-notregistered) | * Если подключение не найдено, то | ||
** Находим в базе 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) | * Если не подвязан ip, то return svc-guest-ipoe(svc-filter-disable) | ||
* В ответ добавляем строку: Framed-IP-Address = хх.хх.хх.хх | * В ответ добавляем строку: Framed-IP-Address = хх.хх.хх.хх | ||
* Если не подвязана услуга типа "интернет", то return svc-guest-ipoe(svc-filter-disable) | * Если не подвязана услуга типа "интернет", то return svc-guest-ipoe(svc-filter-disable) | ||
* Если баланс меньше 0, то return svc-guest-ipoe(svc-filter-nomoney) | * Если баланс меньше 0, то return svc-guest-ipoe(svc-filter-nomoney) | ||
* В ответ добавляем атрибуты из услуги | * В ответ добавляем атрибуты из услуги | ||
==Mysql процедуры== | ==Mysql процедуры== | ||
Рядок 201: | Рядок 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( | 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( | 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; | ||
Рядок 225: | Рядок 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 | ||
WHERE m.mac=usr_mac; | |||
WHERE | |||
END IF; | END IF; | ||
IF( usr_id IS NULL ) THEN | 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 | 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 NULL,encoded_user_name,'Callback-Number',usr_id,'='; | SELECT NULL,encoded_user_name,'Callback-Number',usr_id,'='; | ||
SELECT INET_NTOA(ip) INTO usr_ip FROM ip_pool | SELECT INET_NTOA(ip) INTO usr_ip FROM ip_pool | ||
WHERE uid = usr_id AND type='static' LIMIT 1; | WHERE uid=usr_id AND type='static' | ||
ORDER BY INET_NTOA(ip)=usr_ip DESC LIMIT 1; | |||
IF( usr_ip IS NOT NULL ) THEN | IF( usr_ip IS NOT NULL ) THEN | ||
Рядок 258: | Рядок 371: | ||
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; | |||
IF( add_attr IS NULL OR usr_ip IS NULL ) THEN | 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)','+='; | SELECT NULL,encoded_user_name,'ERX-Service-Activate:1','svc-guest-ipoe-new(svc-filter-disable)','+='; | ||
ELSEIF( | ELSEIF( usr_state = 'off' AND usr_balance < 0 ) THEN | ||
SELECT NULL,encoded_user_name,'ERX-Service-Activate:1','svc-guest-ipoe(svc-filter-nomoney)','+='; | 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 | ELSE | ||
attr_loop: WHILE TRUE DO | attr_loop: WHILE TRUE DO | ||
Рядок 281: | Рядок 398: | ||
</pre> | |||
===radupdate=== | |||
<pre> | |||
DROP PROCEDURE IF EXISTS `radupdate`; | DROP PROCEDURE IF EXISTS `radupdate`; | ||
DELIMITER $$ | DELIMITER $$ | ||
CREATE PROCEDURE `radupdate`( | CREATE PROCEDURE `radupdate`( | ||
IN | 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 | BEGIN | ||
CALL set_auth( | DECLARE user_id INT UNSIGNED; | ||
UPDATE mac_uid SET time=UNIX_TIMESTAMP() WHERE ip=INET_ATON( | SELECT uid INTO user_id FROM ip_pool WHERE ip=INET_ATON(usr_ip); | ||
UPDATE | 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$$ | END$$ | ||
DELIMITER ; | 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`; | DROP PROCEDURE IF EXISTS `radstop`; | ||
DELIMITER $$ | DELIMITER $$ | ||
CREATE PROCEDURE `radstop`(IN | CREATE PROCEDURE `radstop`(IN user_ipa VARCHAR(16), IN nas_ipa VARCHAR(16)) | ||
BEGIN | BEGIN | ||
DECLARE auth_prop VARCHAR(255); | DECLARE auth_prop VARCHAR(255); | ||
DECLARE user_id INT UNSIGNED; | |||
DECLARE start_auth INT UNSIGNED; | DECLARE start_auth INT UNSIGNED; | ||
SELECT | SELECT a.properties, a.start, i.uid INTO auth_prop, start_auth, user_id | ||
FROM | FROM ip_pool i | ||
IF( auth_prop IS NOT NULL ) THEN | 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 | 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= | DELETE FROM auth_now WHERE ip=user_ipa LIMIT 1; | ||
END IF; | END IF; | ||
END$$ | END$$ | ||
DELIMITER ; | DELIMITER ; | ||
</pre> | </pre> | ||
Рядок 322: | Рядок 471: | ||
В соседней консоли тестовый запрос: | В соседней консоли тестовый запрос: | ||
<source lang="bash"> | <source lang="bash"> | ||
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 = "14192" | |||
Framed-IP-Address = 100.66.56.104 | |||
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, '%'). Поэтому:
- mods-enabled/sql в параметре 'nas=%{NAS-IP-Address};...;...' nas на первом месте
- в 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 вставляється запис що треба відрубити юзера з такою сесією
месадж 'Ваш компьютер зарегистрирован'