| « [Часть 2: Cisco 7206 BRAS/SSG] Внедряем профили SSG на Cisco 7206 с целью ограничения скорости и организации псевдо-локалки абонентам PPPoE | Hirahara Ayaka » |
[Часть 1: FreeRADIUS] Внедряем профили SSG на Cisco 7206 с целью ограничения скорости и организации псевдо-локалки абонентам PPPoE
Linux, Сети и администрирование, Cisco Add commentsВ этой статье я вкратце расскажу вам о том, как правильно организовать управление абонентами с помощью SSG на Cisco 7206.
Фактически Cisco 7206 с IOS 12.2SR является полнофункциональным BRAS и SSG (Subscriber Service Gateway), на котором мы можем тонко управлять обслуживанием абонентов, используя функции SSG.
Итак, поставленная задача:
1. Организовать доступ абонентов, приходящих на Q-in-Q VLAN'ах, к сети по протоколу PPPoE.
2. Организовать авторизацию доступа абонентов по протоколу CHAP, используя для авторизации сервер RADIUS.
3. Организовать возможность управления скоростью пропускания трафика для каждого абонента, причем:
3а. Скорость доступа в Internet должна жестко ограничиваться заданным параметром (для каждого абонента в отдельности).
3б. Скорость доступа между абонентами, а также к заданным сетям и ресурсам, должна иметь общее для всех и достаточно высокое ограничение ("локальная" скорость).
4. Организовать возможность независимого отключения каждого конкретного абонента, причем при этом должна сохраняться возможность доступа к отдельным ресурсам (к примеру - сайту и личному кабинету).
5. Организовать возможность учета внешнего трафика абонентов средствами RADIUS, причем трафик пункта 3б ("локальный трафик") учитываться не должен.
Если вас заинтересовало решение поставленной задачи, то читайте далее.
...
Начинаем действовать
Нам потребуются:
- Cisco 7206 NPE-G2 (в принципе, любая Cisco 7200 серии должна подойти) с IOS 12.2SR.
- Готовый к работе Linux-сервер (я строил все на CentOS 5)
- Дистрибутивы FreeRadius 2.x и MySQL 5.x (первое я собирал из исходников, второе - ставил из RPM от разработчиков MySQL)
- Свежая голова и прямые руки (или холодильник и плоскогубцы)
1. Установка и настройка RADIUS
Вначале подготовим наш сервер RADIUS к работе.
Как установить FreeRADIUS c MySQL, я описывать не буду - если уж решились взяться за Cisco 7206 и сервисные службы - значит вполне способны справиться с этой простейшей задачей. Я отмечу только отдельные тонкости установки FreeRADIUS, которые необходимы для корректной работы всей системы.
В словарь FreeRADIUS придется добавить такую строчку:
ATTRIBUTE Client-MAC-Address 3330 string
Она нужна для того, чтобы MAC-адреса клиентов могли корректно записываться в базу данных. Идентификатор (3330) - не важен, этот атрибут определяется по строке в Acct-данных, присылаемой в Cisco-AVPair формате, главное, чтобы он ни с чем не пересекался.
Модуль acct-unique настраиваем следующим образом:
acct_unique {
key = "User-Name, Acct-Session-Id, NAS-IP-Address, Client-IP-Address, NAS-Port"
}
В модуле preprocess не забудьте настроить:
with_cisco_vsa_hack = yes
Это позволит нам нормально аккаунтить Cisco VSA пары атрибут-значение.
После того, как FreeRADIUS установлен и основательно настроен, надо связать его с MySQL (или другой базой на ваш вкус, я привожу конфигурацию для MySQL).
Мы будем использовать следующую схему БД MySQL:
--
-- База данных: `radius`
--
-- --------------------------------------------------------
--
-- Структура таблицы `nas`
--
CREATE TABLE `nas` (
`id` int(10) NOT NULL auto_increment,
`nasname` varchar(128) NOT NULL,
`shortname` varchar(32) default NULL,
`type` varchar(30) default 'other',
`ports` int(5) default NULL,
`secret` varchar(60) NOT NULL default 'secret',
`community` varchar(50) default NULL,
`description` varchar(200) default 'RADIUS Client',
PRIMARY KEY (`id`),
KEY `nasname` (`nasname`)
);
-- --------------------------------------------------------
--
-- Структура таблицы `radacct`
--
CREATE TABLE `radacct` (
`RadAcctId` bigint(21) NOT NULL auto_increment,
`AcctSessionId` varchar(32) NOT NULL default '',
`AcctUniqueId` varchar(32) NOT NULL default '',
`UserName` varchar(64) NOT NULL default '',
`Realm` varchar(64) default '',
`NASIPAddress` varchar(15) NOT NULL default '',
`NASPortId` varchar(15) default NULL,
`NASPortType` varchar(32) default NULL,
`AcctStartTime` datetime default '0000-00-00 00:00:00',
`AcctStopTime` datetime default NULL,
`AcctSessionTime` int(12) default NULL,
`AcctAuthentic` varchar(32) default NULL,
`ConnectInfo_start` varchar(50) default NULL,
`ConnectInfo_stop` varchar(50) default NULL,
`AcctInputOctets` bigint(12) default NULL,
`AcctOutputOctets` bigint(12) default NULL,
`CalledStationId` varchar(50) NOT NULL default '',
`CallingStationId` varchar(50) NOT NULL default '',
`AcctTerminateCause` varchar(32) NOT NULL default '',
`ServiceType` varchar(32) default NULL,
`ServiceInfo` varchar(64) NOT NULL default '',
`FramedProtocol` varchar(32) default NULL,
`FramedIPAddress` varchar(15) NOT NULL default '',
`AcctStartDelay` int(12) default NULL,
`AcctStopDelay` int(12) default NULL,
`ClientMACAddress` varchar(24) NOT NULL default '',
PRIMARY KEY (`RadAcctId`),
KEY `UserName` (`UserName`),
KEY `FramedIPAddress` (`FramedIPAddress`),
KEY `AcctSessionId` (`AcctSessionId`),
KEY `AcctUniqueId` (`AcctUniqueId`),
KEY `AcctStopTime` (`AcctStopTime`),
KEY `NASIPAddress` (`NASIPAddress`),
KEY `AcctStartTime_2` (`AcctStartTime`,`AcctStopTime`),
KEY `summary_index` (`UserName`,`ServiceInfo`(8),`AcctStartTime`,`AcctStopTime`,`AcctInputOctets`,`AcctOutputOctets`,`AcctSessionTime`)
);
-- --------------------------------------------------------
--
-- Структура таблицы `radcheck`
--
CREATE TABLE `radcheck` (
`id` int(11) unsigned NOT NULL auto_increment,
`UserName` varchar(64) NOT NULL default '',
`Attribute` varchar(32) NOT NULL default '',
`op` char(2) NOT NULL default '==',
`Value` varchar(253) NOT NULL default '',
PRIMARY KEY (`id`),
KEY `UserName` (`UserName`(32)),
KEY `UserAttr` (`UserName`,`Attribute`)
);
-- --------------------------------------------------------
--
-- Структура таблицы `radgroupcheck`
--
CREATE TABLE `radgroupcheck` (
`id` int(11) unsigned NOT NULL auto_increment,
`GroupName` varchar(64) NOT NULL default '',
`Attribute` varchar(32) NOT NULL default '',
`op` char(2) NOT NULL default '==',
`Value` varchar(253) NOT NULL default '',
PRIMARY KEY (`id`),
KEY `GroupName` (`GroupName`(32))
);
-- --------------------------------------------------------
--
-- Структура таблицы `radgroupreply`
--
CREATE TABLE `radgroupreply` (
`id` int(11) unsigned NOT NULL auto_increment,
`GroupName` varchar(64) NOT NULL default '',
`Attribute` varchar(32) NOT NULL default '',
`op` char(2) NOT NULL default '=',
`Value` varchar(253) NOT NULL default '',
PRIMARY KEY (`id`),
KEY `GroupName` (`GroupName`(32))
);
-- --------------------------------------------------------
--
-- Структура таблицы `radpostauth`
--
CREATE TABLE `radpostauth` (
`id` int(11) NOT NULL auto_increment,
`user` varchar(64) NOT NULL default '',
`pass` varchar(64) NOT NULL default '',
`reply` varchar(32) NOT NULL default '',
`reply_message` varchar(255) NOT NULL,
`date` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
-- --------------------------------------------------------
--
-- Структура таблицы `radreply`
--
CREATE TABLE `radreply` (
`id` int(11) unsigned NOT NULL auto_increment,
`UserName` varchar(64) NOT NULL default '',
`Attribute` varchar(32) NOT NULL default '',
`op` char(2) NOT NULL default '=',
`Value` varchar(253) NOT NULL default '',
PRIMARY KEY (`id`),
KEY `UserName` (`UserName`(32))
);
-- --------------------------------------------------------
--
-- Структура таблицы `usergroup`
--
CREATE TABLE `usergroup` (
`UserName` varchar(64) NOT NULL default '',
`GroupName` varchar(64) NOT NULL default '',
`priority` int(11) NOT NULL default '1',
KEY `GroupName` (`GroupName`),
KEY `UserGroup` (`UserName`,`GroupName`),
KEY `UserName` (`UserName`,`priority`)
);
Не будем обсуждать здесь выбор конкретных значений и оптимальность базы - если вас что-то не устраивает - вы всегда можете поменять все, что захотите. В моем случае именно такая структура базы оказалась субоптимальной, хотя поле для деятельности, конечно же, еще имеется.
Для работы с этой схемой нам потребуется файл-описатель модуля sql для FreeRADIUS . И вот тут-то кроется самое интересное.
Дело в том, что FreeRADIUS по умолчанию не соблюдает разумный порядок атрибутов, назначенных абоненту. По уму, хотелось бы видеть сначала атрибуты всех групп, назначенных абоненту, и только после - атрибуты самого абонента (чтобы можно было там оверрайдить что-нибудь из групп). Алгоритмы работы с группами в FreeRADIUS нам этого не позволяют.
Поэтому придется извращаться. Не волнуйтесь, данное извращение ни капельки не ухудшает (а может быть - даже улучшает) производительность, зато дает нам некоторую предсказуемость порядка возвращаемых атрибутов.
sql sql_bras {
database = "mysql"
driver = "rlm_sql_${database}"
server = "localhost"
port = 3306
login = "сюда вставляем логин к БД"
password = "сюда вставляем пароль к БД"
radius_db = "сюда вставляем имя БД"
acct_table1 = "radacct"
acct_table2 = "radacct"
postauth_table = "radpostauth"
authcheck_table = "radcheck"
authreply_table = "radreply"
groupcheck_table = "radgroupcheck"
groupreply_table = "radgroupreply"
usergroup_table = "usergroup"
deletestalesessions = yes
sqltrace = no
num_sql_socks = 8
connect_failure_retry_delay = 15
lifetime = 3600
max_queries = 1024
nas_table = "nas"
sql_user_name = "%{User-Name}"
nas_query = "SELECT `id`, `nasname`, `shortname`, `type`, `secret` FROM `${nas_table}`"
authorize_check_query = "( \
SELECT 1 , '%{SQL-User-Name}', `rgc`.`Attribute` , `rgc`.`Value` , `rgc`.`op` \
FROM `${usergroup_table}` AS `ug` \
INNER JOIN `${groupcheck_table}` AS `rgc` ON `rgc`.`GroupName` = `ug`.`GroupName` \
WHERE `ug`.`UserName` = '%{SQL-User-Name}' \
ORDER BY `ug`.`priority` ASC \
) \
UNION \
( \
SELECT 1 , `rc`.`UserName` , `rc`.`Attribute` , `rc`.`Value` , `rc`.`op` \
FROM `${authcheck_table}` AS `rc` \
WHERE `rc`.`UserName` = '%{SQL-User-Name}' \
)"
authorize_reply_query = "( \
SELECT 1 , '%{SQL-User-Name}', `rgc`.`Attribute` , `rgc`.`Value` , `rgc`.`op` \
FROM `${usergroup_table}` AS `ug` \
INNER JOIN `${groupreply_table}` AS `rgc` ON `rgc`.`GroupName` = `ug`.`GroupName` \
WHERE `ug`.`UserName` = '%{SQL-User-Name}' \
ORDER BY `ug`.`priority` ASC \
) \
UNION \
( \
SELECT 1 , `rc`.`UserName` , `rc`.`Attribute` , `rc`.`Value` , `rc`.`op` \
FROM `${authreply_table}` AS `rc` \
WHERE `rc`.`UserName` = '%{SQL-User-Name}' \
)"
accounting_onoff_query = "\
UPDATE `${acct_table1}` \
SET \
`AcctStopTime` = '%S', \
`AcctSessionTime` = unix_timestamp('%S') - unix_timestamp(`AcctStartTime`), \
`AcctTerminateCause` = '%{Acct-Terminate-Cause}', \
`AcctStopDelay` = %{%{Acct-Delay-Time}:-0} \
WHERE `AcctStopTime` IS NULL \
AND `NasIPAddress` = '%{NAS-IP-Address}' \
AND `AcctStartTime` <= '%S'"
accounting_update_query = " \
UPDATE `${acct_table1}` \
SET \
`FramedIPAddress` = '%{Framed-IP-Address}', \
`ClientMACAddress` = '%{Client-MAC-Address}', \
`AcctSessionTime` = '%{Acct-Session-Time}', \
`AcctInputOctets` = '%{%{Acct-Input-Gigawords}:-0}' << 32 | '%{%{Acct-Input-Octets}:-0}', \
`AcctOutputOctets` = '%{%{Acct-Output-Gigawords}:-0}' << 32 | '%{%{Acct-Output-Octets}:-0}' \
WHERE `AcctSessionId` = '%{Acct-Session-Id}' \
AND `UserName` = '%{SQL-User-Name}' \
AND `NasIPAddress` = '%{NAS-IP-Address}'"
accounting_update_query_alt = " \
INSERT INTO `${acct_table1}` \
(`AcctSessionId`, `AcctUniqueId`, `UserName`, \
`Realm`, `NASIPAddress`, `NASPortId`, \
`NASPorttype`, `AcctStartTime`, `AcctSessionTime`, \
`AcctAuthentic`, `ConnectInfo_Start`, `AcctInputOctets`, \
`AcctOutputOctets`, `CalledStationId`, `CallingStationId`, \
`ServiceType`, `FramedProtocol`, `FramedIPAddress`, \
`AcctStartDelay`, `ServiceInfo`, `ClientMACAddress`) \
VALUES \
('%{Acct-Session-Id}', '%{Acct-Unique-Session-Id}', \
'%{SQL-User-Name}', \
'%{Realm}', '%{NAS-IP-Address}', '%{NAS-Port}', \
'%{NAS-Port-Type}', \
DATE_SUB('%S', \
INTERVAL (%{%{Acct-Session-Time}:-0} + \
%{%{Acct-Delay-Time}:-0}) SECOND), \
'%{Acct-Session-Time}', \
'%{Acct-Authentic}', '', \
'%{%{Acct-Input-Gigawords}:-0}' << 32 | \
'%{%{Acct-Input-Octets}:-0}', \
'%{%{Acct-Output-Gigawords}:-0}' << 32 | \
'%{%{Acct-Output-Octets}:-0}', \
'%{Called-Station-Id}', '%{Calling-Station-Id}', \
'%{Service-Type}', '%{Framed-Protocol}', \
'%{Framed-IP-Address}', \
'0', SUBSTRING('%{Cisco-Service-Info}', 2)), \
'%{Client-MAC-Address}'"
accounting_start_query = " \
INSERT INTO `${acct_table1}` \
(`AcctSessionId`, `AcctUniqueId`, `UserName`, \
`Realm`, `NASIPAddress`, `NASPortId`, \
`NASPortType`, `AcctStartTime`, `AcctStopTime`, \
`AcctSessionTime`, `AcctAuthentic`, `ConnectInfo_Start`, \
`ConnectInfo_Stop`, `AcctInputOctets`, `AcctOutputOctets`, \
`CalledStationId`, `CallingStationId`, `AcctTerminateCause`, \
`ServiceType`, `FramedProtocol`, `FramedIPAddress`, \
`AcctStartDelay`, `AcctStopDelay`, `ClientMACAddress`, \
`ServiceInfo`) \
VALUES \
('%{Acct-Session-Id}', '%{Acct-Unique-Session-Id}', \
'%{SQL-User-Name}', \
'%{Realm}', '%{NAS-IP-Address}', '%{NAS-Port}', \
'%{NAS-Port-Type}', '%S', NULL, \
'0', '%{Acct-Authentic}', '%{Connect-Info}', \
'', '0', '0', \
'%{Called-Station-Id}', '%{Calling-Station-Id}', '', \
'%{Service-Type}', '%{Framed-Protocol}', '%{Framed-IP-Address}', \
'%{%{Acct-Delay-Time}:-0}', '0', '%{Client-MAC-Address}', \
SUBSTRING('%{Cisco-Service-Info}', 2))"
accounting_start_query_alt = " \
UPDATE `${acct_table1}` SET \
`AcctStartTime` = '%S', \
`AcctStartDelay` = '%{%{Acct-Delay-Time}:-0}', \
`ConnectInfo_Start` = '%{Connect-Info}', \
`FramedIPAddress` = '%{Framed-IP-Address}' \
`ClientMACAddress` = '%{Client-MAC-Address}' \
WHERE `AcctSessionId` = '%{Acct-Session-Id}' \
AND `UserName` = '%{SQL-User-Name}' \
AND `NASIPAddress` = '%{NAS-IP-Address}'"
accounting_stop_query = " \
UPDATE `${acct_table2}` SET \
`AcctStopTime` = '%S', \
`AcctSessionTime` = '%{Acct-Session-Time}', \
`AcctInputOctets` = '%{%{Acct-Input-Gigawords}:-0}' << 32 | '%{%{Acct-Input-Octets}:-0}', \
`AcctOutputOctets` = '%{%{Acct-Output-Gigawords}:-0}' << 32 | '%{%{Acct-Output-Octets}:-0}', \
`AcctTerminateCause` = '%{Acct-Terminate-Cause}', \
`AcctStopDelay` = '%{%{Acct-Delay-Time}:-0}', \
`ConnectInfo_Stop` = '%{Connect-Info}', \
`FramedIPAddress` = '%{Framed-IP-Address}' \
WHERE `AcctSessionId` = '%{Acct-Session-Id}' \
AND `UserName` = '%{SQL-User-Name}' \
AND `NASIPAddress` = '%{NAS-IP-Address}'"
accounting_stop_query_alt = " \
INSERT INTO `${acct_table2}` \
(`AcctSessionId`, `AcctUniqueId`, `UserName`, \
`Realm`, `NASIPAddress`, `NASPortId`, \
`NASPortType`, `AcctStartTime`, `AcctStopTime`, \
`AcctSessionTime`, `AcctAuthentic`, `ConnectInfo_Start`, \
`ConnectInfo_Stop`, `AcctInputOctets`, `AcctOutputOctets`, \
`CalledStationId`, `CallingStationId`, `AcctTerminateCause`, \
`ServiceType`, `FramedProtocol`, `FramedIPAddress`, \
`AcctStartDelay`, `AcctStopDelay`, `ServiceInfo`) \
VALUES \
('%{Acct-Session-Id}', '%{Acct-Unique-Session-Id}', \
'%{SQL-User-Name}', \
'%{Realm}', '%{NAS-IP-Address}', '%{NAS-Port}', \
'%{NAS-Port-Type}', \
DATE_SUB('%S', \
INTERVAL (%{%{Acct-Session-Time}:-0} + \
%{%{Acct-Delay-Time}:-0}) SECOND), \
'%S', '%{Acct-Session-Time}', '%{Acct-Authentic}', '', \
'%{Connect-Info}', \
'%{%{Acct-Input-Gigawords}:-0}' << 32 | \
'%{%{Acct-Input-Octets}:-0}', \
'%{%{Acct-Output-Gigawords}:-0}' << 32 | \
'%{%{Acct-Output-Octets}:-0}', \
'%{Called-Station-Id}', '%{Calling-Station-Id}', \
'%{Acct-Terminate-Cause}', \
'%{Service-Type}', '%{Framed-Protocol}', '%{Framed-IP-Address}', \
'0', '%{%{Acct-Delay-Time}:-0}', \
SUBSTRING('%{Cisco-Service-Info}', 2))"
simul_count_query = "SELECT COUNT(*) \
FROM `${acct_table1}` \
WHERE `UserName` = '%{SQL-User-Name}' \
AND `AcctStopTime` IS NULL"
simul_verify_query = "SELECT `RadAcctId`, `AcctSessionId`, `UserName`, \
`NASIPAddress`, `NASPortId`, `FramedIPAddress`, \
`CallingStationId`, `FramedProtocol` \
FROM `${acct_table1}` \
WHERE `UserName` = '%{SQL-User-Name}' \
AND `AcctStopTime` IS NULL"
postauth_query = "INSERT INTO ${postauth_table} \
(`user`, `pass`, `reply`, `reply_message`, `date`) \
VALUES ( \
'%{User-Name}', \
'%{%{User-Password}:-%{Chap-Password}}', \
'%{reply:Packet-Type}', '%{reply:Reply-Message}', NOW())"
Хитрые UNION'ы в начале как раз и нужны для "эмуляции" групп.
После того, как FreeRADIUS у вас заработает (radtest вам в руки
) - пора перейти к настройке Cisco. Если вы готовы - читаем следующую часть.