| « [Часть 2: Cisco 7206 BRAS/SSG] Внедряем профили SSG на Cisco 7206 с целью ограничения скорости и организации псевдо-локалки абонентам PPPoE | Hirahara Ayaka » |
[Часть 1: FreeRADIUS] Внедряем профили SSG на Cisco 7206 с целью ограничения скорости и организации псевдо-локалки абонентам PPPoE
В этой статье я вкратце расскажу вам о том, как правильно организовать управление абонентами с помощью 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. Если вы готовы - читаем следующую часть.