RedBack CLIPS
Материал из BiTel WiKi
Rush (Обсуждение | вклад) (→Настройка биллинга) |
Skyb (Обсуждение | вклад) (→Авторизация по VLAN) |
||
(33 промежуточные версии не показаны) | |||
Строка 25: | Строка 25: | ||
Сессия поднимается по Update пакету, т.к. в старт пакете отсутствует IP адрес. | Сессия поднимается по Update пакету, т.к. в старт пакете отсутствует IP адрес. | ||
- | IP адрес перекладывается из атрибута Assigned-IP-Address в стандартный Framed-Ip-Address | + | IP адрес перекладывается из атрибута Assigned-IP-Address в стандартный Framed-Ip-Address скриптом предобработки. |
Из ответа авторизации Framed-Ip-Address удаляется, т.к. иначе редбек не шлёт DHCP запрос. | Из ответа авторизации Framed-Ip-Address удаляется, т.к. иначе редбек не шлёт DHCP запрос. | ||
Строка 137: | Строка 137: | ||
<source lang='bash'> | <source lang='bash'> | ||
- | + | User-Name=f0:7d:68:82:59:27 | |
- | + | NAS-Port-Id=2/1 vlan-id 2217 clips 169782 | |
- | + | NAS-Identifier=Redback-IPOE | |
- | + | User-Password=Redback | |
- | + | NAS-IP-Address=172.16.19.1 | |
- | + | NAS-Port=553650345 | |
- | + | Service-Type=5 | |
- | + | NAS-Port-Type=5 | |
- | + | Platform-Type=4 | |
- | + | Medium-Type=11 | |
- | + | Agent-Remote-Id={01 06 1C BD B9 E6 48 78} | |
- | + | DHCP-Option={3D 3D 07 01 F0 7D 68 82 59 27} | |
- | + | DHCP-Option={0C 0C 07 44 49 52 2D 33 30 30} | |
- | + | Agent-Circuit-Id={00 04 08 A9 00 01} | |
- | + | OS-Version=6.5.1.3 | |
- | + | Mac-Addr=f0-7d-68-82-59-27 | |
- | + | NAS-Real-Port=553650345 | |
- | + | UNKNOWN[3561--1]={02 0A 01 06 1C BD B9 E6 48 78} | |
- | + | UNKNOWN[3561--1]={01 08 00 04 08 A9 00 01} | |
</source> | </source> | ||
В качестве Radius-сервера используется приложение BGInetAccess. | В качестве Radius-сервера используется приложение BGInetAccess. | ||
- | В качестве логина, SE подставляет MAC-адрес абонента. Параметры DHCP-опции 82 хранятся в атрибутах Agent-Remote-Id и Agent-Circuit-Id. Для того, чтобы авторизовать абонента по MAC-адресу коммутатора и порту, необходимо сгенерировать User-Name из этих параметров. Для этого используется динамический класс-обработчик процессора протокола (в биллинге задается в разделе "Типы устройств"), а | + | В качестве логина, SE подставляет MAC-адрес абонента. Параметры DHCP-опции 82 хранятся в атрибутах Agent-Remote-Id и Agent-Circuit-Id. Для того, чтобы авторизовать абонента по MAC-адресу коммутатора и порту, необходимо сгенерировать User-Name из этих параметров. Для этого используется динамический класс-обработчик процессора протокола (в биллинге задается в разделе "Типы устройств"), а метод вызывается в методах обработчика preprocessAccessRequest и preprocessAccountingRequest: |
- | + | ||
<source lang='java'> | <source lang='java'> | ||
- | + | /** | |
- | + | * Установка username | |
- | + | * @param request | |
+ | */ | ||
+ | private void setUsername( RadiusPacket request ) | ||
+ | { | ||
String macAddr = request.getStringAttribute( 2352, 145, null ); | String macAddr = request.getStringAttribute( 2352, 145, null ); | ||
byte[] remoteId = request.getByteAttribute( 2352, 96, null ); | byte[] remoteId = request.getByteAttribute( 2352, 96, null ); | ||
Строка 174: | Строка 176: | ||
String userName = ""; | String userName = ""; | ||
- | + | ||
- | userName = Utils.bytesToHexString( remoteId ).substring(4, | + | if (remoteId.length == 8) { |
+ | logger.info( "Format of Option 82 is D-link" ); | ||
+ | userName = Utils.bytesToHexString( remoteId ).substring(4, 16) + ":" + Utils.bytesToHexString( circuitId ).substring(10, 12); | ||
+ | } | ||
+ | |||
+ | if (remoteId.length == 11) { | ||
+ | logger.info( "Format of Option 82 is Eltex" ); | ||
+ | //userName = Utils.bytesToHexString( remoteId ).substring(10, 22) + Utils.bytesToHexString( circuitId ).substring(4, 8) + ":" + Utils.bytesToHexString( circuitId ).substring(20, 22); | ||
+ | userName = Utils.bytesToHexString( remoteId ).substring(10, 22) + ":" + Utils.bytesToHexString( circuitId ).substring(8, 12); | ||
+ | } | ||
userName = userName.toLowerCase(); | userName = userName.toLowerCase(); | ||
request.setStringAttribute( -1, 1, userName ); | request.setStringAttribute( -1, 1, userName ); | ||
request.setStringAttribute( -1, 31, callingStation ); | request.setStringAttribute( -1, 31, callingStation ); | ||
} | } | ||
- | + | } | |
</source> | </source> | ||
Строка 234: | Строка 245: | ||
== Процесс аккаунтинга == | == Процесс аккаунтинга == | ||
- | После установления сессии, RedBack отправляет на Accounting-сервер сначала Start-пакет, и следом сразу же Update-пакет с Acct-Session-Time=0. Поскольку в Start-пакете отсутствует IP-адрес абонента, | + | После установления сессии, RedBack отправляет на Accounting-сервер сначала Start-пакет, и следом сразу же Update-пакет с Acct-Session-Time=0. Поскольку в Start-пакете отсутствует IP-адрес абонента, он обновляется в соединении по первому Update-пакету. |
IP адрес абонента приходит в VSA 132 ( Assigned-Ip-Address), поэтому для корректной работы биллинга необходимо скопировать его значение в параметр Framed-Ip-Address. | IP адрес абонента приходит в VSA 132 ( Assigned-Ip-Address), поэтому для корректной работы биллинга необходимо скопировать его значение в параметр Framed-Ip-Address. | ||
Строка 240: | Строка 251: | ||
Поскольку при авторизации мы устанавливаем абоненту сервис RSE-SVC-EXT и включаем на нем посервисный аккаунтинг, SmartEdge помимо Accounting-пакетов для всей сессии, будет посылать пакеты для этого сервиса. Эти пакеты отличаются тем, что параметр Acct-Status-Type для Start, Stop и Update-пакетов равен 101, 102 и 103 соответственно. Параметр Acct-Session-Id формируется из Acct-Session-Id родительской сессии плюс строки вида RSE-SVC-EXT-1278BAF8. Идентификатор родительской сессии передается в параметре Acct-Multi-Session-Id. | Поскольку при авторизации мы устанавливаем абоненту сервис RSE-SVC-EXT и включаем на нем посервисный аккаунтинг, SmartEdge помимо Accounting-пакетов для всей сессии, будет посылать пакеты для этого сервиса. Эти пакеты отличаются тем, что параметр Acct-Status-Type для Start, Stop и Update-пакетов равен 101, 102 и 103 соответственно. Параметр Acct-Session-Id формируется из Acct-Session-Id родительской сессии плюс строки вида RSE-SVC-EXT-1278BAF8. Идентификатор родительской сессии передается в параметре Acct-Multi-Session-Id. | ||
- | Таким образом, | + | Таким образом, необходимо указывать биллингу, что аккаунтинг посервисный, указав идентификатор родительской сессии, имя сервиса, по которому идет аккаунтинг, и поменять значение Acct-Status-Type на стандартные 1,2,3. |
- | + | Указанная логика реализуется в методе preprocessAccountingRequest класса SmartEdgeClipsProtocolHandler: | |
- | + | ||
- | + | ||
- | + | ||
- | Указанная логика реализуется в методе preprocessAccountingRequest класса | + | |
<source lang='java'> | <source lang='java'> | ||
@Override | @Override | ||
- | + | public void preprocessAccountingRequest( RadiusPacket request, RadiusPacket response, ConnectionSet connectionSet ) | |
- | + | throws Exception | |
- | + | { | |
- | int acctStatusType = request.getIntAttribute( -1, | + | int acctStatusType = request.getIntAttribute( -1, RadiusDictionary.Acct_Status_Type, -1 ); |
- | + | ||
- | + | switch( acctStatusType ) | |
{ | { | ||
- | + | // если сервисный аккаунтинг | |
+ | case 101: | ||
+ | case 102: | ||
+ | case 103: | ||
+ | { | ||
+ | // получаем id родительского соединения | ||
+ | final String parentAcctSessionId = request.getStringAttribute( -1, parentAcctSessionIdType, null ); | ||
+ | // получаем имя сервиса, по которому идет аккаунтинг | ||
+ | final String serviceName = request.getStringAttribute( radiusVendor, serviceNameType, null ); | ||
+ | |||
+ | logger.debug( "parentAcctSessionId=" + parentAcctSessionId + ", serviceName=" + serviceName ); | ||
- | + | // подменяем Acct-Status-Type, чтобы биллинг понял типы пакетов | |
- | + | request.setIntAttribute( -1, RadiusDictionary.Acct_Status_Type, acctStatusType - 100 ); | |
- | + | // устанавливаем id родительской сессии | |
- | + | request.setOption( InetRadiusProcessor.PARENT_ACCT_SESSION_ID, parentAcctSessionId ); | |
+ | // устанавливаем имя сервиса текущего аккаунтинга | ||
+ | request.setOption( InetRadiusProcessor.SERVICE_NAME, serviceName ); | ||
+ | |||
+ | // если указан сервис, при котором доступ ограничен - проверяем, не его ли это аккаунтинг, | ||
+ | // и, если это так, переключаем состояние соединения | ||
+ | if( Utils.notBlankString( disableServiceName ) && disableServiceName.equals( serviceName ) ) | ||
+ | { | ||
+ | if( acctStatusType == 101 || acctStatusType == 102 ) | ||
+ | { | ||
+ | logger.debug( "State is disable (from start disable service)" ); | ||
+ | request.setOption( InetRadiusProcessor.DEVICE_STATE, InetServ.STATE_DISABLE ); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | logger.debug( "State is enable (from stop disable service)" ); | ||
+ | request.setOption( InetRadiusProcessor.DEVICE_STATE, InetServ.STATE_ENABLE ); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /*Integer ipaddr = request.getIntAttribute( radiusVendor, 132, null ); | ||
+ | if( ipaddr != null ) | ||
+ | { | ||
+ | request.setIntAttribute( -1, RadiusDictionary.Framed_IP_Address, ipaddr ); | ||
+ | }*/ | ||
} | } | ||
- | + | break; | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | default: | |
- | request | + | { |
+ | // для родительского аккаунтинга устанавилваем состояние по наличию определенных атрибутов | ||
+ | setStateFromAttributes( request ); | ||
+ | // устанавливаем поле username | ||
+ | setUsername( request ); | ||
+ | } | ||
+ | break; | ||
} | } | ||
- | + | } | |
</source> | </source> | ||
Строка 295: | Строка 331: | ||
<source lang='bash'> | <source lang='bash'> | ||
- | + | Acct-Session-Id=0100FFFF7800285F-4E7308EE | |
- | + | Service-Name:1=RSE-SVC-EXT | |
- | + | Service-Options:1=1 | |
- | + | Service-Parameter:1=Rate=48000 Burst=6000000 | |
</source> | </source> | ||
Строка 304: | Строка 340: | ||
<source lang='bash'> | <source lang='bash'> | ||
- | + | Acct-Interim-Interval=900 | |
- | + | Acct-Session-Id=0100FFFF78002877-4E7308EE | |
- | + | Forward-Policy=in:NOAUTH-IPOE | |
- | + | HTTP-Redirect-Profile-Name=NOAUTH | |
</source> | </source> | ||
Строка 316: | Строка 352: | ||
Это реализуется в методах connectionModify и connectionClose класса SmartEdgeServiceActivator: | Это реализуется в методах connectionModify и connectionClose класса SmartEdgeServiceActivator: | ||
<source lang='java'> | <source lang='java'> | ||
- | + | @Override | |
- | + | public Object connectionModify( ServiceActivatorEvent e ) | |
- | + | throws Exception | |
- | + | { | |
- | + | logger.info( "Connection modify: oldState: " + e.getOldState() + "; newState: " + e.getNewState() + "; oldOptionSet: " + e.getOldOptions() + "; newOptionSet: " + e.getNewOptions() ); | |
- | InetConnection connection = | + | if( e.getNewState() == InetServ.STATE_DISABLE ) |
+ | { | ||
+ | return connectionClose( e ); | ||
+ | } | ||
+ | |||
+ | InetConnection connection = e.getConnection(); | ||
- | + | if( e.getOldState() == InetServ.STATE_DISABLE ) | |
- | if( | + | |
{ | { | ||
// убрать из DHCP, чтобы выдало NaK | // убрать из DHCP, чтобы выдало NaK | ||
EventProcessor.getInstance().request( new InetConnectionManager.ConnectionRemoveEvent( connection ) ); | EventProcessor.getInstance().request( new InetConnectionManager.ConnectionRemoveEvent( connection ) ); | ||
- | + | ||
return null; | return null; | ||
} | } | ||
- | |||
- | |||
- | |||
- | |||
- | |||
- | + | // закрываем все сервисы | |
+ | RadiusPacket request = radiusClient.createModifyRequest(); | ||
+ | prepareRequest( request, connection ); | ||
+ | request.addAttributes( serviceCloseAttributes ); | ||
- | + | logger.info( "Send CoA: \n" + request ); | |
- | + | radiusClient.send( request ); | |
- | + | ||
- | + | ||
- | + | request = radiusClient.createModifyRequest(); | |
+ | prepareRequest( request, connection ); | ||
+ | |||
+ | // открываем все подключенные сервисы | ||
+ | for( Integer option : e.getNewOptions() ) | ||
+ | { | ||
+ | RadiusAttributeSet set = optionRadiusAttributesMap.get( option ); | ||
+ | if( set != null ) | ||
{ | { | ||
- | + | request.addAttributes( set ); | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
} | } | ||
+ | } | ||
- | + | logger.info( "Send CoA: \n" + request ); | |
- | + | return radiusClient.sendAsync( request ); | |
- | + | } | |
- | + | ||
</source> | </source> | ||
<source lang='java'> | <source lang='java'> | ||
- | + | @Override | |
- | + | public Object connectionClose( ServiceActivatorEvent event ) | |
- | + | throws Exception | |
- | + | { | |
+ | logger.info( "Connection close" ); | ||
InetConnection connection = event.getConnection(); | InetConnection connection = event.getConnection(); | ||
- | RadiusPacket | + | RadiusPacket request = radiusClient.createModifyRequest(); |
- | + | ||
- | + | ||
- | + | prepareRequest( request, connection ); | |
+ | |||
+ | request.addAttributes( disableRadiusAttributes ); | ||
// убрать из DHCP, чтобы выдало NaK | // убрать из DHCP, чтобы выдало NaK | ||
EventProcessor.getInstance().request( new InetConnectionManager.ConnectionRemoveEvent( connection ) ); | EventProcessor.getInstance().request( new InetConnectionManager.ConnectionRemoveEvent( connection ) ); | ||
- | + | logger.info( "Send CoA lock: \n" + request ); | |
- | return | + | return radiusClient.sendAsync( request ); |
- | + | } | |
</source> | </source> | ||
- | == Настройка | + | == Настройка модуля Inet == |
Устанавливаем модуль Inet стандартным способом. | Устанавливаем модуль Inet стандартным способом. | ||
Строка 516: | Строка 556: | ||
В разделе "Трафик" в подразделе "Типы трафика" нужно добавить три вида трафика: Время, внешний входящий трафик, внешний исходящий трафик. | В разделе "Трафик" в подразделе "Типы трафика" нужно добавить три вида трафика: Время, внешний входящий трафик, внешний исходящий трафик. | ||
- | В подразделе "Привязка типов трафика" добавляем привязку, с двумя элементами для входящего и исходящего трафиков. Тип элементов - Radius. Код вендора -2, код элемента 2 и 1 соответственно для входящего и исходящего трафиков. Таком образом мы указываем, что трафик нужно брать из атрибутов Acct-Output-Octets, Acct-Output-Gigawords, Acct-Input-Octets и Acct-Input-Gigawords. | + | В подразделе "Привязка типов трафика" добавляем привязку, с двумя элементами для входящего и исходящего трафиков. Тип элементов - Radius. ServiceName - RSE-SVC-EXT - таким образом значения трафика будут получаться только из сервисной сессии RSE-SVC-EXT (для идентификации имени сервиса в обработчике протокола в предобработке устанавливается имя сервиса request.setOption( InetRadiusProcessor.SERVICE_NAME, serviceName );). Код вендора -2, код элемента 2 и 1 соответственно для входящего и исходящего трафиков. Таком образом мы указываем, что трафик нужно брать из атрибутов Acct-Output-Octets, Acct-Output-Gigawords, Acct-Input-Octets и Acct-Input-Gigawords. |
{| | {| | ||
|- valign=top | |- valign=top | ||
Строка 522: | Строка 562: | ||
|} | |} | ||
- | Далее во | + | В разделе Устройства и ресурсы нужно добавить две категории IP-адресов: одну для работающих абонентов, другую - для отключенных. Важно: конфигурации нужно указывать код категории ресурсов, а не код диапазона. |
+ | {| | ||
+ | |- valign=top | ||
+ | | [[Изображение:Rb_clips_inet_4.png|thumb|600px|IP ресурсы]] | ||
+ | |} | ||
+ | |||
+ | Далее во подразделе "Устройства и ресурсы", нам необходимо добавить два типа устройства: сам RedBack и абонентский коммутатор, например DES-3526. Для RedBack в качестве обработчика активации сервисов указываем нужный SmartEdgeServiceActivator, а в качестве обработчика процессора протокола - SmartEdgeProtocolHandler. (см. раздел "Исходный код"). Для коммутатора во вкладке "Интерфейсы", добавляем необходимое количество интерфейсов (в нашем случае - 24 порта). | ||
Также необходимо добавить тип устройства "Process group", ничего в нем не настраивая. | Также необходимо добавить тип устройства "Process group", ничего в нем не настраивая. | ||
{| | {| | ||
Строка 529: | Строка 575: | ||
| [[Изображение:Rb_clips_inet_2.png|thumb|300px|Типы устройства - коммутатор]] | | [[Изображение:Rb_clips_inet_2.png|thumb|300px|Типы устройства - коммутатор]] | ||
|} | |} | ||
+ | |||
+ | Во вкладке "Устройства", необходимо добавить реальные устройства, используемые в сети в соответствии с логической иерархией. | ||
+ | |||
+ | На самом верхнем уровне добавляется устройство с типом ProcessGroup со следующей конфигурацией: | ||
+ | <source lang='bash'> | ||
+ | #типы устройств - Nas-ов | ||
+ | radius.deviceTypeIds=1 | ||
+ | #типы устройств, являющиеся dhcp relay | ||
+ | dhcp.relay.deviceTypeIds=1 | ||
+ | |||
+ | #количество потоков на worker'а | ||
+ | accounting.worker.1.thread.count=1 | ||
+ | #тарификатор: | ||
+ | #минимальная сумма трафика, при которой тарифицировать соединение | ||
+ | accounting.worker.1.tariffication.1.minDeltaAmount=0 | ||
+ | #пауза между заданиями тарификации | ||
+ | accounting.worker.1.tariffication.1.delay=10 | ||
+ | #максимальное количество тарифицируемых соединений за задание | ||
+ | accounting.worker.1.tariffication.1.batchSize=100 | ||
+ | #трекер (обработка сессий без наработки): | ||
+ | #пауза между заданиями трекинга | ||
+ | accounting.worker.1.tracking.1.delay=20 | ||
+ | #максимальное количество проверенных соединений за задание | ||
+ | accounting.worker.1.tracking.1.batchSize=100 | ||
+ | |||
+ | #количество потоков на worker'а | ||
+ | accounting.worker.2.thread.count=1 | ||
+ | #сброс в базу трафиков и наработки | ||
+ | #минимальная наработка, при которой сбрасывать соединения в базу | ||
+ | accounting.worker.2.flushing.1.minDeltaAccount=0 | ||
+ | #пауза между заданиями сброса в базу | ||
+ | accounting.worker.2.flushing.1.delay=20 | ||
+ | #максимальное количество сброшенных соединений в базу за задание | ||
+ | accounting.worker.2.flushing.1.batchSize=500 | ||
+ | |||
+ | #количество потоков на worker'а | ||
+ | accounting.worker.3.thread.count=1 | ||
+ | #завершатель соединений | ||
+ | #пауза между заданиями | ||
+ | accounting.worker.3.finishing.1.delay=20 | ||
+ | #максимальное количество сброшенных соединений в базу за задание | ||
+ | accounting.worker.3.finishing.1.batchSize=500 | ||
+ | |||
+ | |||
+ | #таймаут перевода соединения в статус suspended при остутствии радиус пакетов | ||
+ | connection.suspend.timeout=900 | ||
+ | #таймаут закрытия соединения при остутствии радиус пакетов (не складывается с connection.suspend.timeout) | ||
+ | connection.close.timeout=900 | ||
+ | #таймаут перевода соединения в статус suspended при остутствии радиус пакетов (для сессий с ограниченным доступом) | ||
+ | #connection.disable.suspend.timeout=900 | ||
+ | #таймаут закрытия соединения при остутствии радиус пакетов (не складывается с connection.suspend.timeout, для сессий с ограниченным доступом) | ||
+ | #connection.disable.close.timeout=900 | ||
+ | </source> | ||
+ | |||
+ | Уровнем ниже добавляем устройство типа RedBack, то есть наш NAS. Указываем IP-адрес (соответствует атрибуту Nas-Ip-Address), идентификатор (соответствует атрибуту NAS-Identifier) и секрет радиуса. Конфигурация устройства: | ||
+ | <source lang='bash'> | ||
+ | #хост для отправки PoD и CoA запросов (по умолчанию - хост, заданный в параметрах устройства Хост/порт) | ||
+ | #radius.host=<хост устройства> | ||
+ | #порт для отправки PoD и CoA запросов (по умолчанию - порт, заданный в параметрах устройства Хост/порт) | ||
+ | #radius.port=<порт устройства> | ||
+ | #идентификатор - Nas-Identifier (по умолчанию - значение из поля Идентификатор параметров устройства) | ||
+ | #radius.identifier=<идентификатор устройства> | ||
+ | #используемый secret для общения по radius-протоколу (по умолчанию - значение из поля Community/secret параметров устройства) | ||
+ | #radius.secret=<community/sercret устройства> | ||
+ | |||
+ | # Атрибуты, которые передаются для всех сессий. | ||
+ | # IP-Interface-Name соответствует интерфейсу в конфиге RedBack, к которому "биндится" абонентская сессия (обязательный параметр). | ||
+ | # DHCP-Max-Leases - обязательный параметр, необходим для получения адреса по DHCP. | ||
+ | const.access.attributes=IP-Interface-Name=CLIENTS;DHCP-Max-Leases=1;Acct-Interim-Interval=900 | ||
+ | |||
+ | # Атрибуты для сессий абонентов, отключенных по балансу. Используются для http-редиректа. | ||
+ | redirect.attributes=HTTP-Redirect-Profile-Name=NOAUTH;Forward-Policy=in:NOAUTH-IPOE;Acct-Interim-Interval=900; | ||
+ | # Наборы атрибутов для разных опций. В данном случае указываются разные скорости доступа | ||
+ | option.1.attributes=Service-Name:1=RSE-SVC-EXT;Service-Options:1=1;Service-Parameter:1=Rate=100000 Burst=12500000 | ||
+ | option.2.attributes=Service-Name:1=RSE-SVC-EXT;Service-Options:1=1;Service-Parameter:1=Rate=10000 Burst=1250000 | ||
+ | |||
+ | #атрибуты, выдаваемые при авторизации по реалму default (default - реалм по умолчанию) | ||
+ | radius.realm.default.attributes= | ||
+ | #категории ip адресов из ресурсов, из которых будут выдаваться адреса ("пул", указывается во вкладке "IP ресурсы") | ||
+ | radius.realm.default.ipCategories=4 | ||
+ | |||
+ | #коды ошибок, при которых вместо reject выдавать accept с заданными атрибутами | ||
+ | #(пользователю выдается серый адрес и устанавливается HTTP-редирект) | ||
+ | radius.disable.accessCodes=1,2,3,4,10,11,12 | ||
+ | #атрибуты, выдаваемые при rejectToAccept | ||
+ | radius.disable.attributes=IP-Interface-Name=NOAUTH;DHCP-Max-Leases=1;{@redirect.attributes} | ||
+ | #категории ip адресов из ресурсов, из которых будут выдаваться адреса для отключенных ("пул", указывается во вкладке "IP ресурсы") | ||
+ | radius.disable.ipCategories=3 | ||
+ | #атрибуты, при наличии которых соединение должно считаться в состоянии DISABLE (т.е. с ограниченным доступом) | ||
+ | # Используемый для отключенных пул адресов и параметры http-редиректа. | ||
+ | radius.disable.pattern.attributes=HTTP-Redirect-Profile-Name=NOAUTH | ||
+ | |||
+ | #привязка кодов опций модуля к атрибутам | ||
+ | #данные атрибуты будут выдаваться в AccessAccept при удачной авторизации и при наличии активных опций в тарифе или сервисе | ||
+ | radius.inetOption.1.attributes={@const.access.attributes};{@option.1.attributes} | ||
+ | radius.inetOption.2.attributes={@const.access.attributes};{@option.2.attributes} | ||
+ | radius.inetOption.3.attributes={@const.access.attributes};{@option.3.attributes} | ||
+ | |||
+ | #-------------- | ||
+ | #параметры dhcp | ||
+ | #-------------- | ||
+ | #настройки DHCP-сервера | ||
+ | dhcp.option.serverIdentifier=0.0.0.0 | ||
+ | dhcp.option.leaseTime=900 | ||
+ | |||
+ | dhcp.net.option.193.106.88.0:255.255.255.0.gate=193.106.88.1 | ||
+ | dhcp.net.option.193.106.88.0:255.255.255.0.dns=194.165.18.6 | ||
+ | |||
+ | dhcp.net.option.172.16.24.0:255.255.255.0.gate=172.16.24.1 | ||
+ | dhcp.net.option.172.16.24.0:255.255.255.0.dns=194.165.18.6 | ||
+ | |||
+ | #---------------------------- | ||
+ | #параметры активации сервисов | ||
+ | #---------------------------- | ||
+ | #длина паузы, если возникла ошибка | ||
+ | #sa.error.pause=60 | ||
+ | #количество заданий за раз | ||
+ | #sa.batch.size=20 | ||
+ | #время (сек) ожидания завершения всех заданий (при асинхронной работе) | ||
+ | #sa.batch.wait=5 | ||
+ | #пауза (сек) после обработки заданий | ||
+ | #sa.batch.pause=0 | ||
+ | #время (сек) ожидания новой задачи перед вызовом disconnect. | ||
+ | #sa.batch.waitNext=5 | ||
+ | |||
+ | #---------------------------------------- | ||
+ | #параметры обработчика активации сервисов | ||
+ | #---------------------------------------- | ||
+ | #откуда при отправке CoA брать атрибуты опций (по умолчанию - те же атрибуты, что выдаются при удачной авторизации) | ||
+ | sa.radius.option.attributesPrefix=option. | ||
+ | sa.radius.connection.attributes=Acct-Session-Id | ||
+ | #атрибуты CoA запроса для прекращения доступа (используется при sa.radius.connection.withoutBreak=1) | ||
+ | sa.radius.disable.attributes={@redirect.attributes} | ||
+ | #фиксированные атрибуты, добавляемые в запрос перед отправкой CoA | ||
+ | #sa.radius.coa.attributes= | ||
+ | #добавлять ли при отправке CoA атрибуты реалма (для default - из radius.realm.default.attributes) | ||
+ | #sa.radius.realm.addAttributes=0 | ||
+ | #атрибуты, посылаемые при закрытии сервисов | ||
+ | sa.radius.service.closeAttributes=Deactivate-Service-Name:1=RSE-SVC-EXT | ||
+ | #фиксированные атрибуты, добавляемые в запрос перед отправкой PoD | ||
+ | #sa.radius.pod.attributes= | ||
+ | </source> | ||
+ | |||
+ | На уровень ниже RedBack добавляем устройство с типом DES-3526. Единственное, что нужно там указать - это идентификатор, который соответствует MAC-адресу коммутатора без разделителей. | ||
+ | {| | ||
+ | |- valign=top | ||
+ | | [[Изображение:Rb_clips_inet_3.png|thumb|600px|Иерархия устройств]] | ||
+ | |} | ||
+ | |||
+ | |||
+ | Далее необходимо создать тип сервиса во вкладке "Типы сервисов". Сервис - это услуга, которая привязывается к договору. В настройках типа сервиса указывается, какие поля будут доступны при добавлении/редактировании сервиса на договоре. В нашем случае это устройство (коммутатор) и интерфейс (порт коммутатора). | ||
+ | {| | ||
+ | |- valign=top | ||
+ | | [[Изображение:Rb_clips_inet_5.png|thumb|600px|Тип сервиса]] | ||
+ | |} | ||
+ | |||
+ | Поскольку аутентификация фактически происходит по логину, нам нужно чтобы при сохранении сервиса, логин генерировался исходя из идентификатора коммутатора и номера порта. Для этого служит динамический класс, привязываемый к событию "изменяется сервис договора". | ||
+ | <source lang='java'> | ||
+ | package ru.clink.bgbilling.module.inet.serv; | ||
+ | |||
+ | import ru.bitel.bgbilling.kernel.script.server.dev.EventScript; | ||
+ | import ru.bitel.bgbilling.kernel.script.server.dev.EventScriptBase; | ||
+ | import ru.bitel.bgbilling.modules.inet.api.common.bean.InetDevice; | ||
+ | import ru.bitel.bgbilling.modules.inet.api.common.bean.InetServ; | ||
+ | import ru.bitel.bgbilling.modules.inet.api.server.bean.InetDeviceDao; | ||
+ | import ru.bitel.bgbilling.modules.inet.api.server.event.InetServChangingEvent; | ||
+ | //import ru.bitel.bgbilling.modules.inet.api.server.event.InetServModifiedEvent; | ||
+ | import ru.bitel.bgbilling.server.util.Setup; | ||
+ | import ru.bitel.common.sql.ConnectionSet; | ||
+ | |||
+ | public class ClipsLoginGenerator | ||
+ | |||
+ | extends EventScriptBase<InetServChangingEvent> | ||
+ | implements EventScript<InetServChangingEvent> { | ||
+ | @Override | ||
+ | public void onEvent( InetServChangingEvent e, Setup setup, ConnectionSet connectionSet ) | ||
+ | throws Exception | ||
+ | { | ||
+ | InetServ inetServ = e.getInetServ(); | ||
+ | int deviceId = inetServ.getDeviceId(); | ||
+ | |||
+ | InetDevice device = new InetDeviceDao( connectionSet.getConnection(), e.getModuleId() ).get( deviceId ); | ||
+ | |||
+ | // сервис SE с коммутатором | ||
+ | if( inetServ.getTypeId() == 1 ) | ||
+ | { | ||
+ | int port = inetServ.getInterfaceId(); | ||
+ | |||
+ | String userName = device.getIdentifier().toLowerCase() + ":" + String.format( "%1$02x", new Object[]{ port } ); | ||
+ | |||
+ | inetServ.setLogin( userName ); | ||
+ | inetServ.setPassword( "Redback" ); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | == Настройка тарифных планов == | ||
+ | Для примера создаем два тарифных плана. Первый - безлимитный, с различной скоростью в разное время суток (устанавливается опциями). Второй - с учетом трафика. | ||
+ | |||
+ | {| | ||
+ | |- valign=top | ||
+ | | [[Изображение:Rb_clips_inet_6.png|thumb|600px|Безлимитный тарифный план]] | ||
+ | |} | ||
+ | |||
+ | {| | ||
+ | |- valign=top | ||
+ | | [[Изображение:Rb_clips_inet_7.png|thumb|600px|Тарифный план с учетом трафика]] | ||
+ | |} | ||
+ | |||
+ | == Настройка договора == | ||
+ | В договоре необходимо добавить модуль Inet. Добавление сервиса интуитивно понятно. Сначала выбираем устройство (коммутатор), затем - интерфейс (порт). Прежде чем добавлять сервис, необходимо добавить на договор скрипт поведения, который сгенерирует логин. | ||
+ | |||
+ | |||
+ | == Исходный код == | ||
+ | |||
+ | Последние актуальные версии классов добавляются в динамические классы вместе со сборкой модуля: | ||
+ | |||
+ | '''ru.bitel.bgbilling.modules.inet.dyn.device.redback.SmartEdgeClipsProtocolHandler''' | ||
+ | |||
+ | '''ru.bitel.bgbilling.modules.inet.dyn.device.redback.SmartEdgeClipsServiceActivator''' | ||
+ | |||
+ | |||
+ | Класс активации сервиса | ||
+ | |||
+ | <source lang='java'> | ||
+ | package ru.clink.bgbilling.module.inet.device.redback; | ||
+ | |||
+ | import java.net.InetAddress; | ||
+ | import java.util.Map; | ||
+ | |||
+ | |||
+ | import org.apache.log4j.Logger; | ||
+ | |||
+ | import ru.bitel.bgbilling.kernel.event.EventProcessor; | ||
+ | import ru.bitel.bgbilling.kernel.network.radius.RadiusAttributeSet; | ||
+ | import ru.bitel.bgbilling.kernel.network.radius.RadiusDictionary; | ||
+ | import ru.bitel.bgbilling.kernel.network.radius.RadiusPacket; | ||
+ | import ru.bitel.bgbilling.modules.inet.access.InetConnectionManager; | ||
+ | import ru.bitel.bgbilling.modules.inet.access.sa.ServiceActivatorAdapter; | ||
+ | import ru.bitel.bgbilling.modules.inet.access.sa.ServiceActivatorEvent; | ||
+ | import ru.bitel.bgbilling.modules.inet.api.common.bean.InetConnection; | ||
+ | import ru.bitel.bgbilling.modules.inet.api.common.bean.InetDevice; | ||
+ | import ru.bitel.bgbilling.modules.inet.api.common.bean.InetDeviceType; | ||
+ | import ru.bitel.bgbilling.modules.inet.api.common.bean.InetServ; | ||
+ | import ru.bitel.bgbilling.modules.inet.radius.PodSupport; | ||
+ | import ru.bitel.bgbilling.server.util.Setup; | ||
+ | import ru.bitel.common.ParameterMap; | ||
+ | |||
+ | public class SmartEdgeServiceActivator | ||
+ | extends ServiceActivatorAdapter | ||
+ | { | ||
+ | private static final Logger log = Logger.getLogger( SmartEdgeServiceActivator.class ); | ||
+ | |||
+ | private PodSupport pod; | ||
+ | private RadiusAttributeSet lockAttributes; | ||
+ | private Map<Integer, RadiusAttributeSet> optionSets; | ||
+ | |||
+ | private RadiusAttributeSet serviceCloseAttributes; | ||
+ | |||
+ | @Override | ||
+ | public Object init( Setup setup, int moduleId, InetDevice device, InetDeviceType deviceType, ParameterMap deviceParams ) | ||
+ | throws Exception | ||
+ | { | ||
+ | String nasHost = deviceParams.get( "nas.radius.host", device.getHost() ); | ||
+ | InetAddress nasHostAddr = InetAddress.getByName( nasHost ); | ||
+ | int nasPort = deviceParams.getInt( "nas.radius.port", 1700 ); | ||
+ | |||
+ | byte[] nasSecret = deviceParams.get( "nas.secret", device.getSecret() ).getBytes(); | ||
+ | |||
+ | pod = new PodSupport( nasHostAddr, nasPort, nasSecret ); | ||
+ | |||
+ | log.info( "Init script for device: " + device.getId() ); | ||
+ | |||
+ | // атрибуты отправляются в CoA при необходимости сброса | ||
+ | lockAttributes = RadiusAttributeSet.newRadiusAttributeSet( deviceParams.get( "redirect.attributes", "" ) ); | ||
+ | |||
+ | // RADIUS атрибуты по ключу опции | ||
+ | optionSets = RadiusAttributeSet.newRadiusAttributeSetMap( deviceParams, "option.", "attributes" ); | ||
+ | |||
+ | serviceCloseAttributes = RadiusAttributeSet.newRadiusAttributeSet( deviceParams.get( "close.attributes", "Deactivate-Service-Name:1=RSE-SVC-EXT" ) ); | ||
+ | |||
+ | log.info( "Options map size: " + optionSets.size() ); | ||
+ | |||
+ | return null; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public Object destroy() | ||
+ | throws Exception | ||
+ | { | ||
+ | pod.destroy(); | ||
+ | return null; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public Object connectionClose( ServiceActivatorEvent event ) | ||
+ | throws Exception | ||
+ | { | ||
+ | log.info( "Connection close!" ); | ||
+ | |||
+ | InetConnection connection = event.getConnection(); | ||
+ | RadiusPacket packet = pod.createModifyRequest(); | ||
+ | |||
+ | preparePacket( packet, connection ); | ||
+ | |||
+ | packet.addAttributes( lockAttributes ); | ||
+ | |||
+ | // убрать из DHCP, чтобы выдало NaK | ||
+ | EventProcessor.getInstance().request( new InetConnectionManager.ConnectionRemoveEvent( connection ) ); | ||
+ | |||
+ | log.info( "Send CoA lock: \n" + packet ); | ||
+ | |||
+ | return pod.sendAsync( packet ); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public Object connectionModify( ServiceActivatorEvent event ) | ||
+ | throws Exception | ||
+ | { | ||
+ | log.info( "Connection modify!" ); | ||
+ | log.info( "oldState: " + event.getOldState() + "; newState: " + event.getNewState() + "; oldOptionSet: " + event.getOldOptions() + "; newOptionSet: " + event.getNewOptions() ); | ||
+ | |||
+ | InetConnection connection = event.getConnection(); | ||
+ | |||
+ | // это Reject-To-Accept коннект, нужно сбросить для инициации нормального коннекта | ||
+ | if( event.getOldState() == InetServ.STATE_DISABLE && event.getNewState() == InetServ.STATE_ENABLE ) | ||
+ | { | ||
+ | // убрать из DHCP, чтобы выдало NaK | ||
+ | EventProcessor.getInstance().request( new InetConnectionManager.ConnectionRemoveEvent( connection ) ); | ||
+ | |||
+ | return null; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | RadiusPacket packet = pod.createModifyRequest(); | ||
+ | packet.addAttributes( serviceCloseAttributes ); | ||
+ | preparePacket( packet, connection ); | ||
+ | |||
+ | log.info( "Send CoA: \n" + packet ); | ||
+ | |||
+ | pod.send( packet ); | ||
+ | |||
+ | packet = pod.createModifyRequest(); | ||
+ | |||
+ | preparePacket( packet, connection ); | ||
+ | |||
+ | for( Integer optionId : event.getNewOptions() ) | ||
+ | { | ||
+ | RadiusAttributeSet attrs = optionSets.get( optionId ); | ||
+ | if( attrs != null ) | ||
+ | { | ||
+ | packet.addAttributes( attrs ); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | log.info( "Send CoA: \n" + packet ); | ||
+ | |||
+ | return pod.sendAsync( packet ); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | private void preparePacket( RadiusPacket packet, InetConnection connection ) | ||
+ | { | ||
+ | packet.setStringAttribute( -1, RadiusDictionary.Acct_Session_Id, connection.getAcctSessionId() ); | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | Класс обработчика процессора протокола | ||
+ | |||
+ | <source lang='java'> | ||
+ | package ru.clink.bgbilling.module.inet.device.redback; | ||
+ | import org.apache.log4j.Logger; | ||
+ | import ru.bitel.bgbilling.kernel.network.dhcp.*; | ||
+ | import ru.bitel.bgbilling.kernel.network.radius.*; | ||
+ | import ru.bitel.bgbilling.modules.inet.access.sa.*; | ||
+ | import ru.bitel.common.*; | ||
+ | import ru.bitel.common.sql.*; | ||
+ | |||
+ | public class SmartEdgeProtocolHandler | ||
+ | extends ProtocolHandlerAdapter | ||
+ | { | ||
+ | private static final Logger log = Logger.getLogger( SmartEdgeServiceActivator.class ); | ||
+ | @Override | ||
+ | public void preprocessAccessRequest( RadiusPacket request, RadiusPacket response, ConnectionSet connectionSet ) | ||
+ | throws Exception | ||
+ | { | ||
+ | String macAddr = request.getStringAttribute( 2352, 145, null ); | ||
+ | byte[] remoteId = request.getByteAttribute( 2352, 96, null ); | ||
+ | byte[] circuitId = request.getByteAttribute( 2352, 97, null ); | ||
+ | |||
+ | if( macAddr != null && remoteId != null && circuitId != null ) | ||
+ | { | ||
+ | String callingStation = macAddr.replaceAll( "\\-", "" ); | ||
+ | |||
+ | String userName = ""; | ||
+ | |||
+ | if (remoteId.length == 8) { | ||
+ | log.info( "Format of Option 82 is D-link" ); | ||
+ | userName = Utils.bytesToHexString( remoteId ).substring(4, 16) + ":" + Utils.bytesToHexString( circuitId ).substring(10, 12); | ||
+ | } | ||
+ | |||
+ | userName = userName.toLowerCase(); | ||
+ | request.setStringAttribute( -1, 1, userName ); | ||
+ | request.setStringAttribute( -1, 31, callingStation ); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void postprocessAccessRequest( RadiusPacket request, RadiusPacket response, ConnectionSet connectionSet ) | ||
+ | throws Exception | ||
+ | { | ||
+ | response.removeAttributes( -1, 8 ); | ||
+ | } | ||
+ | |||
+ | |||
+ | @Override | ||
+ | public void preprocessAccountingRequest( RadiusPacket request, RadiusPacket response, ConnectionSet connectionSet ) | ||
+ | throws Exception | ||
+ | { | ||
+ | int acctStatusType = request.getIntAttribute( -1, 40, 0 ); | ||
+ | // старты получается не обрабатываем, сессия стартует по апдейту | ||
+ | if( acctStatusType != 1 && acctStatusType != 101 ) | ||
+ | { | ||
+ | preprocessAccessRequest( request, response, connectionSet ); | ||
+ | |||
+ | Integer ipaddr = request.getIntAttribute( 2352, 132, null ); | ||
+ | if( ipaddr != null ) | ||
+ | { | ||
+ | request.setIntAttribute( -1, 8, ipaddr ); | ||
+ | } | ||
+ | } | ||
+ | if ( acctStatusType == 3 || acctStatusType == 2) { | ||
+ | // обнуляем общие счетчики трафика (нас интересуют только посервисные) | ||
+ | request.setIntAttribute(-1, 42, 0); | ||
+ | request.setIntAttribute(-1, 43, 0); | ||
+ | } | ||
+ | |||
+ | if ( acctStatusType == 103 || acctStatusType == 102) { | ||
+ | String sessionID = request.getStringAttribute(-1, 50, null); | ||
+ | request.setStringAttribute(-1, 44, sessionID); | ||
+ | } | ||
+ | |||
+ | |||
+ | if ( acctStatusType == 103 || acctStatusType == 102) { | ||
+ | request.setIntAttribute(-1, 40, 3); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | @Override | ||
+ | public void preprocessDhcpRequest( DhcpPacket request, DhcpPacket response ) | ||
+ | throws Exception | ||
+ | { | ||
+ | //String userName = Utils.bytesToHexString( remoteId ).substring(4, 16) + ":" + Utils.bytesToHexString( circuitId ).substring(10, 12); | ||
+ | try { | ||
+ | byte[] circuitId = request.getSubOption( (byte)1 ).value; | ||
+ | byte[] remoteId = request.getSubOption( (byte)2 ).value; | ||
+ | if (remoteId.length == 8) { | ||
+ | byte[] mac = new byte[6]; | ||
+ | byte[] port = new byte[1]; | ||
+ | System.arraycopy(circuitId, 5, port, 0, 1); | ||
+ | System.arraycopy(remoteId, 2, mac, 0, 6); | ||
+ | request.setSubOption( (byte)1, port); | ||
+ | request.setSubOption( (byte)2, mac); | ||
+ | } | ||
+ | } catch (java.lang.NullPointerException e) { | ||
+ | return; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | </source> | ||
+ | |||
+ | Класс генерации логина | ||
+ | |||
+ | <source lang='java'> | ||
+ | package ru.clink.bgbilling.module.inet.serv; | ||
+ | |||
+ | import ru.bitel.bgbilling.kernel.script.server.dev.EventScript; | ||
+ | import ru.bitel.bgbilling.kernel.script.server.dev.EventScriptBase; | ||
+ | import ru.bitel.bgbilling.modules.inet.api.common.bean.InetDevice; | ||
+ | import ru.bitel.bgbilling.modules.inet.api.common.bean.InetServ; | ||
+ | import ru.bitel.bgbilling.modules.inet.api.server.bean.InetDeviceDao; | ||
+ | import ru.bitel.bgbilling.modules.inet.api.server.event.InetServChangingEvent; | ||
+ | //import ru.bitel.bgbilling.modules.inet.api.server.event.InetServModifiedEvent; | ||
+ | import ru.bitel.bgbilling.server.util.Setup; | ||
+ | import ru.bitel.common.sql.ConnectionSet; | ||
+ | |||
+ | public class ClipsLoginGenerator | ||
+ | |||
+ | extends EventScriptBase<InetServChangingEvent> | ||
+ | implements EventScript<InetServChangingEvent> { | ||
+ | @Override | ||
+ | public void onEvent( InetServChangingEvent e, Setup setup, ConnectionSet connectionSet ) | ||
+ | throws Exception | ||
+ | { | ||
+ | InetServ inetServ = e.getInetServ(); | ||
+ | int deviceId = inetServ.getDeviceId(); | ||
+ | |||
+ | InetDevice device = new InetDeviceDao( connectionSet.getConnection(), e.getModuleId() ).get( deviceId ); | ||
+ | |||
+ | // сервис SE с коммутатором | ||
+ | if( inetServ.getTypeId() == 1 ) | ||
+ | { | ||
+ | int port = inetServ.getInterfaceId(); | ||
+ | |||
+ | String userName = device.getIdentifier().toLowerCase() + ":" + String.format( "%1$02x", new Object[]{ port } ); | ||
+ | |||
+ | inetServ.setLogin( userName ); | ||
+ | inetServ.setPassword( "Redback" ); | ||
+ | } | ||
+ | |||
+ | // сервис SE + GePON | ||
+ | if( inetServ.getTypeId() == 2 ) | ||
+ | { | ||
+ | int vlan = inetServ.getVlan(); | ||
+ | |||
+ | String userName = device.getIdentifier().toLowerCase() + ":" + String.format( "%1$04x", new Object[]{ vlan } ); | ||
+ | |||
+ | inetServ.setLogin( userName ); | ||
+ | inetServ.setPassword( "Redback" ); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | == Авторизация по VLAN == | ||
+ | |||
+ | Для того чтоб была возможность авторизироваться по VLAN+MAC было сделано следующие | ||
+ | |||
+ | |||
+ | конфиг redback | ||
+ | radius.servSearchMode=2 - поиск по vlan на устройстве | ||
+ | dhcp.key.pattern=$deviceId:$mac - чтобы радиус и дхцп привязывались по SE+mac устройства (т.к. в dhcp пакетах не нашел информации о vlan) | ||
+ | radius.agent.option.remoteId.type=0 | ||
+ | radius.agent.option.circuitId.type=0 - чтобы SmartEdgeProtocolHandler не искал и не подставлял agentRemoteId (чтобы не искалось дочернее агентское устройство) и agentCircuitId (т.к. vlan подставляем вручную в коде) в радиус пакете | ||
+ | расширен SmartEdgeProtocolHandler - сделано SmartEdgeVlanProtocolHandler, который просто vlanId подставляет | ||
+ | <source lang='java'> | ||
+ | package ru.dinkor.bgbilling.module.inet.dyn.device.redback; | ||
+ | |||
+ | import java.util.regex.Matcher; | ||
+ | import java.util.regex.Pattern; | ||
+ | |||
+ | import org.apache.log4j.Logger; | ||
+ | |||
+ | import ru.bitel.bgbilling.kernel.network.radius.RadiusPacket; | ||
+ | import ru.bitel.bgbilling.modules.inet.dyn.device.redback.SmartEdgeClipsProtocolHandler; | ||
+ | import ru.bitel.bgbilling.modules.inet.radius.InetRadiusProcessor; | ||
+ | |||
+ | public class SmartEdgeClipsVlanProtocolHandler | ||
+ | extends SmartEdgeClipsProtocolHandler | ||
+ | { | ||
+ | private static final Logger logger = Logger.getLogger( SmartEdgeClipsVlanProtocolHandler.class ); | ||
+ | |||
+ | private final Pattern vlanPattern = Pattern.compile( "(.+) vlan-id (\\d+)" ); | ||
+ | |||
+ | @Override | ||
+ | protected void setAgentOptions( RadiusPacket request ) | ||
+ | { | ||
+ | super.setAgentOptions( request ); | ||
+ | |||
+ | String nasPortId = request.getStringAttribute( -1, 87, null ); | ||
+ | if( nasPortId != null ) | ||
+ | { | ||
+ | Matcher m = vlanPattern.matcher( nasPortId ); | ||
+ | if( m.find() ) | ||
+ | { | ||
+ | String vlanId = m.group( 2 ); | ||
+ | |||
+ | logger.info( "vlanId=" + vlanId ); | ||
+ | |||
+ | request.setOption( InetRadiusProcessor.VLAN_ID, vlanId ); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | logger.error( "vlanId not found!" ); | ||
+ | } | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | logger.error( "Attribute Nas-Port-Id not found!" ); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | </source> | ||
+ | |||
== Ссылки == | == Ссылки == | ||
#[http://forum.bgbilling.ru/viewtopic.php?f=44&t=5361&start=15 Обсуждение на форуме] | #[http://forum.bgbilling.ru/viewtopic.php?f=44&t=5361&start=15 Обсуждение на форуме] |
Текущая версия на 02:16, 21 июня 2012
CLIPS - это технология, используемая в оборудовании фирмы Ericsson для аутентификации, авторизации и аккаунтинга абонентов IPOE. Принципы CLIPS подробно описаны в документе "Clips HOWTO" ( http://shop.nag.ru/article/clips ).
В данной статье будет описана интеграция маршрутизатора SE100 с АСР BGBilling для работы с абонентами CLIPS.
Аутентификации абонентов происходит по порту коммутатора с использованием DHCP опции 82. В нашем случае SE100 является основным шлюзом и релеем для абонентов. Используется посервисный аккаунтинг, причем учитывается только внешний трафик.
Содержание |
Схема сети
Клиент - Коммутатор с Options.82 - SE100 - Мир | Сервер биллинга
Коммутатор проставляет Options.82 свой идентификатор, порт клиента и пересылает запрос на SE100 широковещательно. Из сети 172.16.23.0/24 выдаётся IP адрес авторизованным клиентам, из 172.16.25.0/24 - блокированным с установкой редиректа на страницу HTTP с ошибкой.
При необходимости блокировки сессии клиента устанавливается редирект на текущую сессию посредством CoA, делается пометка в DHCP сервере о истечении аренды. По следующему DHCP запросу отправляется NaK, после чего редбек завершает сессию и начинает новую. В новой сессии выдаётся уже адрес из пула фейковых адресов 172.16.25.0/24.
Сессия поднимается по Update пакету, т.к. в старт пакете отсутствует IP адрес. IP адрес перекладывается из атрибута Assigned-IP-Address в стандартный Framed-Ip-Address скриптом предобработки.
Из ответа авторизации Framed-Ip-Address удаляется, т.к. иначе редбек не шлёт DHCP запрос.
Трафики считаются суммарные по данным атрибутов Acct-Input/Output-Octets, Acct-Input/Output-Gigawords.
Процесс аутентификации
Когда клиент инициирует получение адреса, коммутатор вставляет в запрос опцию 82 и запрос попадает на интерфейс SE100.
Конфиг коммутатора (для примера возьмем D-link 3526):
# dhcp_local_relay позволяет вставлять в приходящие от абонентов DHCP-запросы опцию 82. enable dhcp_local_relay config dhcp_local_relay vlanid 2000 state enable # 2000 - вилан, настроенный на абонентских портах
Конфиг SE100:
context IPOE ! no ip domain-lookup ! # Интерфейс, к которому будут "биндиться" абоненты после авторизации interface CLIENTS multibind ip address 193.x.x.1/24 dhcp proxy 65535 ip arp secured-arp ! # Интерфейс, к которому будут "биндиться" абоненты, не прошедшие авторизацию (аналог Reject-To-Accept) interface NOAUTH multibind ip address 172.16.24.1/24 dhcp proxy 65535 ! # Интерфейс для общения с Radius-сервером interface loop0 loopback ip address 172.16.19.1/32 ip source-address radius dhcp-server no logging console logging syslog 194.xx.xx.2 facility local7 ! # Акцесс-лист для абонентов, не прошедших авторизацию. Разрешает работу DNS и DHCP, а также позволяет выйти на страницу управления и сайт провайдера. policy access-list HTTP-REDIRECT seq 10 permit tcp any host 194.xx.xx.10 eq www class CLS-NORMAL seq 19 permit udp any host 194.xx.xx.6 eq domain class CLS-NORMAL seq 20 permit tcp any host 194.xx.xx.3 eq www class CLS-NORMAL seq 21 permit udp any eq bootpc host 193.xx.xx.1 eq bootps class CLS-NORMAL seq 22 permit udp any eq bootpc host 172.16.24.1 eq bootps class CLS-NORMAL seq 30 permit tcp any any eq www class CLS-REDIRECT seq 40 permit ip any any class CLS-DROP ! # Классы для разделения трафика по зонам (локальный и внешний) policy access-list SUB-IN seq 10 permit ip any 194.xx.xx.0 0.0.1.255 class INTERNAL seq 20 permit ip any 193.xx.xx.0 0.0.3.255 class INTERNAL seq 30 permit ip any any class EXTERNAL ! policy access-list SUB-OUT seq 10 permit ip 194.xx.xx.0 0.0.1.255 any class INTERNAL seq 20 permit ip 193.xx.xx.0 0.0.3.255 any class INTERNAL seq 30 permit ip any any class EXTERNAL ! # Профиль http-редиректа (всех не прошедних авторизацию перенаправлять на страницу с ошибкой) http-redirect profile NOAUTH url "http://noauth.provider.ru/" # Настройки Radius aaa authentication administrator local aaa authentication administrator maximum sessions 1 aaa authentication subscriber radius aaa accounting subscriber radius aaa update subscriber 10 aaa accounting event dhcp aaa hint ip-address radius accounting server 194.xx.xx.10 encrypted-key 5FD62606082DAF92 port 11813 radius coa server 194.xx.xx.10 encrypted-key 5FD62606082DAF92 port 3799 ! radius server 194.xx.xx.10 encrypted-key 5FD62606082DAF92 port 11812 radius attribute nas-ip-address interface loop0 radius attribute nas-port format session-info radius attribute nas-identifier Redback-IPOE ! # Полиси по умолчанию для абонента (переопределяется через Radius-атрибуты) subscriber default qos policy policing DEF-IPOE-IN qos policy metering DEF-IPOE-OUT ! # Профиль сервиса. В нем задаются параметры подключения (скорость), а также указывается на то, что учет трафика необходимо вести по qos классу EXTERNAL radius service profile RSE-SVC-EXT parameter value Rate 100000 parameter value Burst 12500000 accounting in qos EXTERNAL accounting out qos EXTERNAL seq 10 attribute Dynamic-Policy-Filter "ip in forward class EXTERNAL qos" seq 20 attribute Dynamic-Policy-Filter "ip out forward class EXTERNAL qos" seq 30 attribute Dynamic-Qos-Parameter "meter-class-rate EXTERNAL rate-absolute $Rate" seq 40 attribute Dynamic-Qos-Parameter "meter-class-burst EXTERNAL $Burst" seq 50 attribute Dynamic-Qos-Parameter "police-class-rate EXTERNAL rate-absolute $Rate" seq 60 attribute Dynamic-Qos-Parameter "police-class-burst EXTERNAL $Burst" seq 70 attribute Service-Interim-Accounting 900 ! ip route 0.0.0.0/0 context local ! ! # Адрес DHCP-сервера dhcp relay server 194.165.18.17
После получения DHCP DISCOVER запроса, RedBack преобразует параметры запроса в Radius-атрибуты и отправляет Access-Request на указанный Radius-сервер. Запрос выглядит примерно следующим образом:
User-Name=f0:7d:68:82:59:27 NAS-Port-Id=2/1 vlan-id 2217 clips 169782 NAS-Identifier=Redback-IPOE User-Password=Redback NAS-IP-Address=172.16.19.1 NAS-Port=553650345 Service-Type=5 NAS-Port-Type=5 Platform-Type=4 Medium-Type=11 Agent-Remote-Id={01 06 1C BD B9 E6 48 78} DHCP-Option={3D 3D 07 01 F0 7D 68 82 59 27} DHCP-Option={0C 0C 07 44 49 52 2D 33 30 30} Agent-Circuit-Id={00 04 08 A9 00 01} OS-Version=6.5.1.3 Mac-Addr=f0-7d-68-82-59-27 NAS-Real-Port=553650345 UNKNOWN[3561--1]={02 0A 01 06 1C BD B9 E6 48 78} UNKNOWN[3561--1]={01 08 00 04 08 A9 00 01}
В качестве Radius-сервера используется приложение BGInetAccess. В качестве логина, SE подставляет MAC-адрес абонента. Параметры DHCP-опции 82 хранятся в атрибутах Agent-Remote-Id и Agent-Circuit-Id. Для того, чтобы авторизовать абонента по MAC-адресу коммутатора и порту, необходимо сгенерировать User-Name из этих параметров. Для этого используется динамический класс-обработчик процессора протокола (в биллинге задается в разделе "Типы устройств"), а метод вызывается в методах обработчика preprocessAccessRequest и preprocessAccountingRequest:
/** * Установка username * @param request */ private void setUsername( RadiusPacket request ) { String macAddr = request.getStringAttribute( 2352, 145, null ); byte[] remoteId = request.getByteAttribute( 2352, 96, null ); byte[] circuitId = request.getByteAttribute( 2352, 97, null ); if( macAddr != null && remoteId != null && circuitId != null ) { String callingStation = macAddr.replaceAll( "\\-", "" ); String userName = ""; if (remoteId.length == 8) { logger.info( "Format of Option 82 is D-link" ); userName = Utils.bytesToHexString( remoteId ).substring(4, 16) + ":" + Utils.bytesToHexString( circuitId ).substring(10, 12); } if (remoteId.length == 11) { logger.info( "Format of Option 82 is Eltex" ); //userName = Utils.bytesToHexString( remoteId ).substring(10, 22) + Utils.bytesToHexString( circuitId ).substring(4, 8) + ":" + Utils.bytesToHexString( circuitId ).substring(20, 22); userName = Utils.bytesToHexString( remoteId ).substring(10, 22) + ":" + Utils.bytesToHexString( circuitId ).substring(8, 12); } userName = userName.toLowerCase(); request.setStringAttribute( -1, 1, userName ); request.setStringAttribute( -1, 31, callingStation ); } }
При успешной аутентификации, BGInetAccess отправляет Access-Accept следующего вида:
Acct-Interim-Interval=900 Framed-IP-Address=193.106.88.119 # Атрибут, необходимый для получения абонентом IP-адреса DHCP-Max-Leases=1 # Атрибуты для включения сервиса для абонента. Устанавливают скорость соединения и включают посервисный аккаунтинг. Service-Name:1=RSE-SVC-EXT Service-Options:1=1 Service-Parameter:1=Rate=6000 Burst=750000 # Обязательный атрибут, указывает, к какому IP-интерфейсу "привязать" абонента IP-Interface-Name=CLIENTS
Для получения адреса по DHCP, в Radius ответе не должно быть атрибута Framed-IP-Address. Поэтому используется следующий метод для обработки ответа:
public void postprocessAccessRequest( RadiusPacket request, RadiusPacket response, ConnectionSet connectionSet ) throws Exception { response.removeAttributes( -1, 8 ); }
После успешной авторизации, SE посылает запрос на DHCP сервер. В качестве сервера DHCP используется то же самое приложение BGInetAccess. Для успешной привязки DHCP-запроса к сессии, необходимо предобработать его в соответствии с выбранным алгоритмом:
@Override public void preprocessDhcpRequest( DhcpPacket request, DhcpPacket response ) throws Exception { try { byte[] circuitId = request.getSubOption( (byte)1 ).value; byte[] remoteId = request.getSubOption( (byte)2 ).value; byte[] mac = new byte[6]; byte[] port = new byte[1]; System.arraycopy(circuitId, 5, port, 0, 1); System.arraycopy(remoteId, 2, mac, 0, 6); request.setSubOption( (byte)1, port); request.setSubOption( (byte)2, mac); } catch (java.lang.NullPointerException e) { return; } }
После этого абонент получает адрес и начинает работать.
Процесс аккаунтинга
После установления сессии, RedBack отправляет на Accounting-сервер сначала Start-пакет, и следом сразу же Update-пакет с Acct-Session-Time=0. Поскольку в Start-пакете отсутствует IP-адрес абонента, он обновляется в соединении по первому Update-пакету.
IP адрес абонента приходит в VSA 132 ( Assigned-Ip-Address), поэтому для корректной работы биллинга необходимо скопировать его значение в параметр Framed-Ip-Address.
Поскольку при авторизации мы устанавливаем абоненту сервис RSE-SVC-EXT и включаем на нем посервисный аккаунтинг, SmartEdge помимо Accounting-пакетов для всей сессии, будет посылать пакеты для этого сервиса. Эти пакеты отличаются тем, что параметр Acct-Status-Type для Start, Stop и Update-пакетов равен 101, 102 и 103 соответственно. Параметр Acct-Session-Id формируется из Acct-Session-Id родительской сессии плюс строки вида RSE-SVC-EXT-1278BAF8. Идентификатор родительской сессии передается в параметре Acct-Multi-Session-Id.
Таким образом, необходимо указывать биллингу, что аккаунтинг посервисный, указав идентификатор родительской сессии, имя сервиса, по которому идет аккаунтинг, и поменять значение Acct-Status-Type на стандартные 1,2,3.
Указанная логика реализуется в методе preprocessAccountingRequest класса SmartEdgeClipsProtocolHandler:
@Override public void preprocessAccountingRequest( RadiusPacket request, RadiusPacket response, ConnectionSet connectionSet ) throws Exception { int acctStatusType = request.getIntAttribute( -1, RadiusDictionary.Acct_Status_Type, -1 ); switch( acctStatusType ) { // если сервисный аккаунтинг case 101: case 102: case 103: { // получаем id родительского соединения final String parentAcctSessionId = request.getStringAttribute( -1, parentAcctSessionIdType, null ); // получаем имя сервиса, по которому идет аккаунтинг final String serviceName = request.getStringAttribute( radiusVendor, serviceNameType, null ); logger.debug( "parentAcctSessionId=" + parentAcctSessionId + ", serviceName=" + serviceName ); // подменяем Acct-Status-Type, чтобы биллинг понял типы пакетов request.setIntAttribute( -1, RadiusDictionary.Acct_Status_Type, acctStatusType - 100 ); // устанавливаем id родительской сессии request.setOption( InetRadiusProcessor.PARENT_ACCT_SESSION_ID, parentAcctSessionId ); // устанавливаем имя сервиса текущего аккаунтинга request.setOption( InetRadiusProcessor.SERVICE_NAME, serviceName ); // если указан сервис, при котором доступ ограничен - проверяем, не его ли это аккаунтинг, // и, если это так, переключаем состояние соединения if( Utils.notBlankString( disableServiceName ) && disableServiceName.equals( serviceName ) ) { if( acctStatusType == 101 || acctStatusType == 102 ) { logger.debug( "State is disable (from start disable service)" ); request.setOption( InetRadiusProcessor.DEVICE_STATE, InetServ.STATE_DISABLE ); } else { logger.debug( "State is enable (from stop disable service)" ); request.setOption( InetRadiusProcessor.DEVICE_STATE, InetServ.STATE_ENABLE ); } } /*Integer ipaddr = request.getIntAttribute( radiusVendor, 132, null ); if( ipaddr != null ) { request.setIntAttribute( -1, RadiusDictionary.Framed_IP_Address, ipaddr ); }*/ } break; default: { // для родительского аккаунтинга устанавилваем состояние по наличию определенных атрибутов setStateFromAttributes( request ); // устанавливаем поле username setUsername( request ); } break; } }
Управление сессиями
Для смены параметров подключения абонента, используются запросы CoA. В нашем случае осуществляется либо изменение скорости соединения, либо отключение абонента.
В случае изменения скорости, необходимо отправить на Smart Edge два запроса: сначала отключить сервис, а затем тут же включить с новыми параметрами.
Acct-Session-Id=0100FFFF7800285F-4E7308EE Deactivate-Service-Name:1=RSE-SVC-EXT
Acct-Session-Id=0100FFFF7800285F-4E7308EE Service-Name:1=RSE-SVC-EXT Service-Options:1=1 Service-Parameter:1=Rate=48000 Burst=6000000
При отключении абонента по балансу, через CoA абонентской сессии устанавливаются параметры редиректа на страницу с ошибкой. Запрос выглядит следующим образом:
Acct-Interim-Interval=900 Acct-Session-Id=0100FFFF78002877-4E7308EE Forward-Policy=in:NOAUTH-IPOE HTTP-Redirect-Profile-Name=NOAUTH
Для того, чтобы пользователь получил адрес из "серой" сети, ему выдается DHCP NAK.
При включении абонента, ему также выдается DHCP NAK чтобы инициализировать получение нового адреса.
Это реализуется в методах connectionModify и connectionClose класса SmartEdgeServiceActivator:
@Override public Object connectionModify( ServiceActivatorEvent e ) throws Exception { logger.info( "Connection modify: oldState: " + e.getOldState() + "; newState: " + e.getNewState() + "; oldOptionSet: " + e.getOldOptions() + "; newOptionSet: " + e.getNewOptions() ); if( e.getNewState() == InetServ.STATE_DISABLE ) { return connectionClose( e ); } InetConnection connection = e.getConnection(); if( e.getOldState() == InetServ.STATE_DISABLE ) { // убрать из DHCP, чтобы выдало NaK EventProcessor.getInstance().request( new InetConnectionManager.ConnectionRemoveEvent( connection ) ); return null; } // закрываем все сервисы RadiusPacket request = radiusClient.createModifyRequest(); prepareRequest( request, connection ); request.addAttributes( serviceCloseAttributes ); logger.info( "Send CoA: \n" + request ); radiusClient.send( request ); request = radiusClient.createModifyRequest(); prepareRequest( request, connection ); // открываем все подключенные сервисы for( Integer option : e.getNewOptions() ) { RadiusAttributeSet set = optionRadiusAttributesMap.get( option ); if( set != null ) { request.addAttributes( set ); } } logger.info( "Send CoA: \n" + request ); return radiusClient.sendAsync( request ); }
@Override public Object connectionClose( ServiceActivatorEvent event ) throws Exception { logger.info( "Connection close" ); InetConnection connection = event.getConnection(); RadiusPacket request = radiusClient.createModifyRequest(); prepareRequest( request, connection ); request.addAttributes( disableRadiusAttributes ); // убрать из DHCP, чтобы выдало NaK EventProcessor.getInstance().request( new InetConnectionManager.ConnectionRemoveEvent( connection ) ); logger.info( "Send CoA lock: \n" + request ); return radiusClient.sendAsync( request ); }
Настройка модуля Inet
Устанавливаем модуль Inet стандартным способом.
Скачиваем и устанавливаем приложения BGInetAccess и BGInetAccounting.
Конфигурация BGInetAccess (inet-access.xml):
<?xml version="1.0" encoding="UTF-8"?> <application context="access"> <param name="app.name" value="BGInetAccess"/> <param name="app.id" value="1"/> <param name="moduleId" value="15"/> <param name="db.driver" value="com.mysql.jdbc.Driver"/> <param name="db.url" value="jdbc:mysql://db.provider.ru/bgbilling?useUnicode=true&characterEncoding=Cp1251&allowUrlInLocalInfile=true&zeroDateTimeBehavior=convertToNull&jdbcCompliantTruncation=false&queryTimeoutKillsConnection=true"/> <param name="db.user" value="bgbilling"/> <param name="db.pswd" value="xxxxxx"/> <param name="mq.url" value="failover:(tcp://localhost:61616)"/> <param name="mq.user" value="bill"/> <param name="mq.pswd" value="xxxxxx"/> <param name="rootDeviceId" value="3"/> <!-- типы фейковых устройств, являющихся аккаунтинг серверами --> <param name="accounting.deviceTypeIds" value="3"/> <param name="commonIdentifierName" value="rootDeviceId" /> <bean name="access" class="ru.bitel.bgbilling.modules.inet.access.Access" /> <param name="datalog.radius.dir" value="data/radius"/> <param name="datalog.dhcp.dir" value="data/dhcp" /> <context name="radius"> <bean name="radiusProcessor" class="ru.bitel.bgbilling.modules.inet.radius.InetRadiusProcessor"/> <scheduledExecutorService name="hrlydtlggr" corePoolSize="1" /> <bean name="radiusDataLogger" class="ru.bitel.bgbilling.modules.inet.radius.RadiusHourlyDataLogger"> <param name="scheduledExecutor">hrlydtlggr</param> </bean> <bean name="radiusListener" class="ru.bitel.bgbilling.modules.inet.radius.InetRadiusListener"> <constructor> <param name="host" value="194.xx.xx.10"/> <param name="port" value="11812"/> <param name="byteBufferCapacity">512 * 1024</param> <param name="processor">radiusProcessor</param> <param name="mode">RadiusListener.Mode.authentication</param> <param name="dataLogger">radiusDataLogger</param> <param name="threadCount">10</param> <param name="maxQueueSize">200</param> </constructor> </bean> </context> <context name="dhcp"> <bean name="dhcpProcessor" class="ru.bitel.bgbilling.modules.inet.dhcp.InetDhcpHelperProcessor"/> <bean name="dhcpListener" class="ru.bitel.bgbilling.kernel.network.dhcp.DhcpListener"> <constructor> <param name="host" value="194.xx.xx.10"/> <param name="port" value="67"/> <param name="byteBufferCapacity">512 * 1024</param> <param name="processor">dhcpProcessor</param> <param name="dataLogger"></param> <param name="threadCount">10</param> <param name="maxQueueSize">200</param> </constructor> </bean> </context> </application>
Конфигурация BGInetAccounting (inet-accounting.xml):
<?xml version="1.0" encoding="UTF-8"?> <application context="accounting"> <param name="app.name" value="BGInetAccounting"/> <param name="app.id" value="2"/> <param name="moduleId" value="15"/> <param name="db.driver" value="com.mysql.jdbc.Driver"/> <param name="db.url" value="jdbc:mysql://db.provider.ru/bgbilling?useUnicode=true&characterEncoding=Cp1251&allowUrlInLocalInfile=true&zeroDateTimeBehavior=convertToNull&jdbcCompliantTruncation=false&queryTimeoutKillsConnection=true"/> <param name="db.user" value="bgbilling"/> <param name="db.pswd" value="xxxxxx"/> <param name="mq.url" value="failover:(tcp://localhost:61616)"/> <param name="mq.user" value="bill"/> <param name="mq.pswd" value="xxxxxx"/> <param name="rootDeviceId" value="3"/> <param name="commonIdentifierName" value="rootDeviceId"/> <bean name="accounting" class="ru.bitel.bgbilling.modules.inet.accounting.Accounting"/> <param name="datalog.radius.dir" value="data/radius" /> <param name="datalog.flow.dir" value="data/flow" /> <context name="radius"> <bean name="processor" class="ru.bitel.bgbilling.modules.inet.radius.InetRadiusProcessor"/> <scheduledExecutorService name="hrlydtlggr" corePoolSize="1"/> <bean name="radiusDataLogger" class="ru.bitel.bgbilling.modules.inet.radius.RadiusHourlyDataLogger"> <param name="scheduledExecutor">hrlydtlggr</param> </bean> <bean name="radiusListener" class="ru.bitel.bgbilling.modules.inet.radius.InetRadiusListener"> <constructor> <param name="host" value="194.xx.xx.10"/> <param name="port" value="11813"/> <param name="byteBufferCapacity">512 * 1024</param> <param name="processor">processor</param> <param name="mode">RadiusListener.Mode.accounting</param> <param name="setup">setup</param> <param name="dataLogger">radiusDataLogger</param> </constructor> </bean> </context> </application>
Заходим во вкладку "Опции" модуля. В нашем случае каждая опция отвечает за определенную скорость доступа. Сами параметры скорости указываются далее, в конфигурации устройств. Здесь необходимо лишь добавить нужное количество опций с удобными для восприятия названиями (например "10Mbit").
В разделе "Трафик" в подразделе "Типы трафика" нужно добавить три вида трафика: Время, внешний входящий трафик, внешний исходящий трафик. В подразделе "Привязка типов трафика" добавляем привязку, с двумя элементами для входящего и исходящего трафиков. Тип элементов - Radius. ServiceName - RSE-SVC-EXT - таким образом значения трафика будут получаться только из сервисной сессии RSE-SVC-EXT (для идентификации имени сервиса в обработчике протокола в предобработке устанавливается имя сервиса request.setOption( InetRadiusProcessor.SERVICE_NAME, serviceName );). Код вендора -2, код элемента 2 и 1 соответственно для входящего и исходящего трафиков. Таком образом мы указываем, что трафик нужно брать из атрибутов Acct-Output-Octets, Acct-Output-Gigawords, Acct-Input-Octets и Acct-Input-Gigawords.
В разделе Устройства и ресурсы нужно добавить две категории IP-адресов: одну для работающих абонентов, другую - для отключенных. Важно: конфигурации нужно указывать код категории ресурсов, а не код диапазона.
Далее во подразделе "Устройства и ресурсы", нам необходимо добавить два типа устройства: сам RedBack и абонентский коммутатор, например DES-3526. Для RedBack в качестве обработчика активации сервисов указываем нужный SmartEdgeServiceActivator, а в качестве обработчика процессора протокола - SmartEdgeProtocolHandler. (см. раздел "Исходный код"). Для коммутатора во вкладке "Интерфейсы", добавляем необходимое количество интерфейсов (в нашем случае - 24 порта). Также необходимо добавить тип устройства "Process group", ничего в нем не настраивая.
Во вкладке "Устройства", необходимо добавить реальные устройства, используемые в сети в соответствии с логической иерархией.
На самом верхнем уровне добавляется устройство с типом ProcessGroup со следующей конфигурацией:
#типы устройств - Nas-ов radius.deviceTypeIds=1 #типы устройств, являющиеся dhcp relay dhcp.relay.deviceTypeIds=1 #количество потоков на worker'а accounting.worker.1.thread.count=1 #тарификатор: #минимальная сумма трафика, при которой тарифицировать соединение accounting.worker.1.tariffication.1.minDeltaAmount=0 #пауза между заданиями тарификации accounting.worker.1.tariffication.1.delay=10 #максимальное количество тарифицируемых соединений за задание accounting.worker.1.tariffication.1.batchSize=100 #трекер (обработка сессий без наработки): #пауза между заданиями трекинга accounting.worker.1.tracking.1.delay=20 #максимальное количество проверенных соединений за задание accounting.worker.1.tracking.1.batchSize=100 #количество потоков на worker'а accounting.worker.2.thread.count=1 #сброс в базу трафиков и наработки #минимальная наработка, при которой сбрасывать соединения в базу accounting.worker.2.flushing.1.minDeltaAccount=0 #пауза между заданиями сброса в базу accounting.worker.2.flushing.1.delay=20 #максимальное количество сброшенных соединений в базу за задание accounting.worker.2.flushing.1.batchSize=500 #количество потоков на worker'а accounting.worker.3.thread.count=1 #завершатель соединений #пауза между заданиями accounting.worker.3.finishing.1.delay=20 #максимальное количество сброшенных соединений в базу за задание accounting.worker.3.finishing.1.batchSize=500 #таймаут перевода соединения в статус suspended при остутствии радиус пакетов connection.suspend.timeout=900 #таймаут закрытия соединения при остутствии радиус пакетов (не складывается с connection.suspend.timeout) connection.close.timeout=900 #таймаут перевода соединения в статус suspended при остутствии радиус пакетов (для сессий с ограниченным доступом) #connection.disable.suspend.timeout=900 #таймаут закрытия соединения при остутствии радиус пакетов (не складывается с connection.suspend.timeout, для сессий с ограниченным доступом) #connection.disable.close.timeout=900
Уровнем ниже добавляем устройство типа RedBack, то есть наш NAS. Указываем IP-адрес (соответствует атрибуту Nas-Ip-Address), идентификатор (соответствует атрибуту NAS-Identifier) и секрет радиуса. Конфигурация устройства:
#хост для отправки PoD и CoA запросов (по умолчанию - хост, заданный в параметрах устройства Хост/порт) #radius.host=<хост устройства> #порт для отправки PoD и CoA запросов (по умолчанию - порт, заданный в параметрах устройства Хост/порт) #radius.port=<порт устройства> #идентификатор - Nas-Identifier (по умолчанию - значение из поля Идентификатор параметров устройства) #radius.identifier=<идентификатор устройства> #используемый secret для общения по radius-протоколу (по умолчанию - значение из поля Community/secret параметров устройства) #radius.secret=<community/sercret устройства> # Атрибуты, которые передаются для всех сессий. # IP-Interface-Name соответствует интерфейсу в конфиге RedBack, к которому "биндится" абонентская сессия (обязательный параметр). # DHCP-Max-Leases - обязательный параметр, необходим для получения адреса по DHCP. const.access.attributes=IP-Interface-Name=CLIENTS;DHCP-Max-Leases=1;Acct-Interim-Interval=900 # Атрибуты для сессий абонентов, отключенных по балансу. Используются для http-редиректа. redirect.attributes=HTTP-Redirect-Profile-Name=NOAUTH;Forward-Policy=in:NOAUTH-IPOE;Acct-Interim-Interval=900; # Наборы атрибутов для разных опций. В данном случае указываются разные скорости доступа option.1.attributes=Service-Name:1=RSE-SVC-EXT;Service-Options:1=1;Service-Parameter:1=Rate=100000 Burst=12500000 option.2.attributes=Service-Name:1=RSE-SVC-EXT;Service-Options:1=1;Service-Parameter:1=Rate=10000 Burst=1250000 #атрибуты, выдаваемые при авторизации по реалму default (default - реалм по умолчанию) radius.realm.default.attributes= #категории ip адресов из ресурсов, из которых будут выдаваться адреса ("пул", указывается во вкладке "IP ресурсы") radius.realm.default.ipCategories=4 #коды ошибок, при которых вместо reject выдавать accept с заданными атрибутами #(пользователю выдается серый адрес и устанавливается HTTP-редирект) radius.disable.accessCodes=1,2,3,4,10,11,12 #атрибуты, выдаваемые при rejectToAccept radius.disable.attributes=IP-Interface-Name=NOAUTH;DHCP-Max-Leases=1;{@redirect.attributes} #категории ip адресов из ресурсов, из которых будут выдаваться адреса для отключенных ("пул", указывается во вкладке "IP ресурсы") radius.disable.ipCategories=3 #атрибуты, при наличии которых соединение должно считаться в состоянии DISABLE (т.е. с ограниченным доступом) # Используемый для отключенных пул адресов и параметры http-редиректа. radius.disable.pattern.attributes=HTTP-Redirect-Profile-Name=NOAUTH #привязка кодов опций модуля к атрибутам #данные атрибуты будут выдаваться в AccessAccept при удачной авторизации и при наличии активных опций в тарифе или сервисе radius.inetOption.1.attributes={@const.access.attributes};{@option.1.attributes} radius.inetOption.2.attributes={@const.access.attributes};{@option.2.attributes} radius.inetOption.3.attributes={@const.access.attributes};{@option.3.attributes} #-------------- #параметры dhcp #-------------- #настройки DHCP-сервера dhcp.option.serverIdentifier=0.0.0.0 dhcp.option.leaseTime=900 dhcp.net.option.193.106.88.0:255.255.255.0.gate=193.106.88.1 dhcp.net.option.193.106.88.0:255.255.255.0.dns=194.165.18.6 dhcp.net.option.172.16.24.0:255.255.255.0.gate=172.16.24.1 dhcp.net.option.172.16.24.0:255.255.255.0.dns=194.165.18.6 #---------------------------- #параметры активации сервисов #---------------------------- #длина паузы, если возникла ошибка #sa.error.pause=60 #количество заданий за раз #sa.batch.size=20 #время (сек) ожидания завершения всех заданий (при асинхронной работе) #sa.batch.wait=5 #пауза (сек) после обработки заданий #sa.batch.pause=0 #время (сек) ожидания новой задачи перед вызовом disconnect. #sa.batch.waitNext=5 #---------------------------------------- #параметры обработчика активации сервисов #---------------------------------------- #откуда при отправке CoA брать атрибуты опций (по умолчанию - те же атрибуты, что выдаются при удачной авторизации) sa.radius.option.attributesPrefix=option. sa.radius.connection.attributes=Acct-Session-Id #атрибуты CoA запроса для прекращения доступа (используется при sa.radius.connection.withoutBreak=1) sa.radius.disable.attributes={@redirect.attributes} #фиксированные атрибуты, добавляемые в запрос перед отправкой CoA #sa.radius.coa.attributes= #добавлять ли при отправке CoA атрибуты реалма (для default - из radius.realm.default.attributes) #sa.radius.realm.addAttributes=0 #атрибуты, посылаемые при закрытии сервисов sa.radius.service.closeAttributes=Deactivate-Service-Name:1=RSE-SVC-EXT #фиксированные атрибуты, добавляемые в запрос перед отправкой PoD #sa.radius.pod.attributes=
На уровень ниже RedBack добавляем устройство с типом DES-3526. Единственное, что нужно там указать - это идентификатор, который соответствует MAC-адресу коммутатора без разделителей.
Далее необходимо создать тип сервиса во вкладке "Типы сервисов". Сервис - это услуга, которая привязывается к договору. В настройках типа сервиса указывается, какие поля будут доступны при добавлении/редактировании сервиса на договоре. В нашем случае это устройство (коммутатор) и интерфейс (порт коммутатора).
Поскольку аутентификация фактически происходит по логину, нам нужно чтобы при сохранении сервиса, логин генерировался исходя из идентификатора коммутатора и номера порта. Для этого служит динамический класс, привязываемый к событию "изменяется сервис договора".
package ru.clink.bgbilling.module.inet.serv; import ru.bitel.bgbilling.kernel.script.server.dev.EventScript; import ru.bitel.bgbilling.kernel.script.server.dev.EventScriptBase; import ru.bitel.bgbilling.modules.inet.api.common.bean.InetDevice; import ru.bitel.bgbilling.modules.inet.api.common.bean.InetServ; import ru.bitel.bgbilling.modules.inet.api.server.bean.InetDeviceDao; import ru.bitel.bgbilling.modules.inet.api.server.event.InetServChangingEvent; //import ru.bitel.bgbilling.modules.inet.api.server.event.InetServModifiedEvent; import ru.bitel.bgbilling.server.util.Setup; import ru.bitel.common.sql.ConnectionSet; public class ClipsLoginGenerator extends EventScriptBase<InetServChangingEvent> implements EventScript<InetServChangingEvent> { @Override public void onEvent( InetServChangingEvent e, Setup setup, ConnectionSet connectionSet ) throws Exception { InetServ inetServ = e.getInetServ(); int deviceId = inetServ.getDeviceId(); InetDevice device = new InetDeviceDao( connectionSet.getConnection(), e.getModuleId() ).get( deviceId ); // сервис SE с коммутатором if( inetServ.getTypeId() == 1 ) { int port = inetServ.getInterfaceId(); String userName = device.getIdentifier().toLowerCase() + ":" + String.format( "%1$02x", new Object[]{ port } ); inetServ.setLogin( userName ); inetServ.setPassword( "Redback" ); } } }
Настройка тарифных планов
Для примера создаем два тарифных плана. Первый - безлимитный, с различной скоростью в разное время суток (устанавливается опциями). Второй - с учетом трафика.
Настройка договора
В договоре необходимо добавить модуль Inet. Добавление сервиса интуитивно понятно. Сначала выбираем устройство (коммутатор), затем - интерфейс (порт). Прежде чем добавлять сервис, необходимо добавить на договор скрипт поведения, который сгенерирует логин.
Исходный код
Последние актуальные версии классов добавляются в динамические классы вместе со сборкой модуля:
ru.bitel.bgbilling.modules.inet.dyn.device.redback.SmartEdgeClipsProtocolHandler
ru.bitel.bgbilling.modules.inet.dyn.device.redback.SmartEdgeClipsServiceActivator
Класс активации сервиса
package ru.clink.bgbilling.module.inet.device.redback; import java.net.InetAddress; import java.util.Map; import org.apache.log4j.Logger; import ru.bitel.bgbilling.kernel.event.EventProcessor; import ru.bitel.bgbilling.kernel.network.radius.RadiusAttributeSet; import ru.bitel.bgbilling.kernel.network.radius.RadiusDictionary; import ru.bitel.bgbilling.kernel.network.radius.RadiusPacket; import ru.bitel.bgbilling.modules.inet.access.InetConnectionManager; import ru.bitel.bgbilling.modules.inet.access.sa.ServiceActivatorAdapter; import ru.bitel.bgbilling.modules.inet.access.sa.ServiceActivatorEvent; import ru.bitel.bgbilling.modules.inet.api.common.bean.InetConnection; import ru.bitel.bgbilling.modules.inet.api.common.bean.InetDevice; import ru.bitel.bgbilling.modules.inet.api.common.bean.InetDeviceType; import ru.bitel.bgbilling.modules.inet.api.common.bean.InetServ; import ru.bitel.bgbilling.modules.inet.radius.PodSupport; import ru.bitel.bgbilling.server.util.Setup; import ru.bitel.common.ParameterMap; public class SmartEdgeServiceActivator extends ServiceActivatorAdapter { private static final Logger log = Logger.getLogger( SmartEdgeServiceActivator.class ); private PodSupport pod; private RadiusAttributeSet lockAttributes; private Map<Integer, RadiusAttributeSet> optionSets; private RadiusAttributeSet serviceCloseAttributes; @Override public Object init( Setup setup, int moduleId, InetDevice device, InetDeviceType deviceType, ParameterMap deviceParams ) throws Exception { String nasHost = deviceParams.get( "nas.radius.host", device.getHost() ); InetAddress nasHostAddr = InetAddress.getByName( nasHost ); int nasPort = deviceParams.getInt( "nas.radius.port", 1700 ); byte[] nasSecret = deviceParams.get( "nas.secret", device.getSecret() ).getBytes(); pod = new PodSupport( nasHostAddr, nasPort, nasSecret ); log.info( "Init script for device: " + device.getId() ); // атрибуты отправляются в CoA при необходимости сброса lockAttributes = RadiusAttributeSet.newRadiusAttributeSet( deviceParams.get( "redirect.attributes", "" ) ); // RADIUS атрибуты по ключу опции optionSets = RadiusAttributeSet.newRadiusAttributeSetMap( deviceParams, "option.", "attributes" ); serviceCloseAttributes = RadiusAttributeSet.newRadiusAttributeSet( deviceParams.get( "close.attributes", "Deactivate-Service-Name:1=RSE-SVC-EXT" ) ); log.info( "Options map size: " + optionSets.size() ); return null; } @Override public Object destroy() throws Exception { pod.destroy(); return null; } @Override public Object connectionClose( ServiceActivatorEvent event ) throws Exception { log.info( "Connection close!" ); InetConnection connection = event.getConnection(); RadiusPacket packet = pod.createModifyRequest(); preparePacket( packet, connection ); packet.addAttributes( lockAttributes ); // убрать из DHCP, чтобы выдало NaK EventProcessor.getInstance().request( new InetConnectionManager.ConnectionRemoveEvent( connection ) ); log.info( "Send CoA lock: \n" + packet ); return pod.sendAsync( packet ); } @Override public Object connectionModify( ServiceActivatorEvent event ) throws Exception { log.info( "Connection modify!" ); log.info( "oldState: " + event.getOldState() + "; newState: " + event.getNewState() + "; oldOptionSet: " + event.getOldOptions() + "; newOptionSet: " + event.getNewOptions() ); InetConnection connection = event.getConnection(); // это Reject-To-Accept коннект, нужно сбросить для инициации нормального коннекта if( event.getOldState() == InetServ.STATE_DISABLE && event.getNewState() == InetServ.STATE_ENABLE ) { // убрать из DHCP, чтобы выдало NaK EventProcessor.getInstance().request( new InetConnectionManager.ConnectionRemoveEvent( connection ) ); return null; } else { RadiusPacket packet = pod.createModifyRequest(); packet.addAttributes( serviceCloseAttributes ); preparePacket( packet, connection ); log.info( "Send CoA: \n" + packet ); pod.send( packet ); packet = pod.createModifyRequest(); preparePacket( packet, connection ); for( Integer optionId : event.getNewOptions() ) { RadiusAttributeSet attrs = optionSets.get( optionId ); if( attrs != null ) { packet.addAttributes( attrs ); } } log.info( "Send CoA: \n" + packet ); return pod.sendAsync( packet ); } } private void preparePacket( RadiusPacket packet, InetConnection connection ) { packet.setStringAttribute( -1, RadiusDictionary.Acct_Session_Id, connection.getAcctSessionId() ); } }
Класс обработчика процессора протокола
package ru.clink.bgbilling.module.inet.device.redback; import org.apache.log4j.Logger; import ru.bitel.bgbilling.kernel.network.dhcp.*; import ru.bitel.bgbilling.kernel.network.radius.*; import ru.bitel.bgbilling.modules.inet.access.sa.*; import ru.bitel.common.*; import ru.bitel.common.sql.*; public class SmartEdgeProtocolHandler extends ProtocolHandlerAdapter { private static final Logger log = Logger.getLogger( SmartEdgeServiceActivator.class ); @Override public void preprocessAccessRequest( RadiusPacket request, RadiusPacket response, ConnectionSet connectionSet ) throws Exception { String macAddr = request.getStringAttribute( 2352, 145, null ); byte[] remoteId = request.getByteAttribute( 2352, 96, null ); byte[] circuitId = request.getByteAttribute( 2352, 97, null ); if( macAddr != null && remoteId != null && circuitId != null ) { String callingStation = macAddr.replaceAll( "\\-", "" ); String userName = ""; if (remoteId.length == 8) { log.info( "Format of Option 82 is D-link" ); userName = Utils.bytesToHexString( remoteId ).substring(4, 16) + ":" + Utils.bytesToHexString( circuitId ).substring(10, 12); } userName = userName.toLowerCase(); request.setStringAttribute( -1, 1, userName ); request.setStringAttribute( -1, 31, callingStation ); } } @Override public void postprocessAccessRequest( RadiusPacket request, RadiusPacket response, ConnectionSet connectionSet ) throws Exception { response.removeAttributes( -1, 8 ); } @Override public void preprocessAccountingRequest( RadiusPacket request, RadiusPacket response, ConnectionSet connectionSet ) throws Exception { int acctStatusType = request.getIntAttribute( -1, 40, 0 ); // старты получается не обрабатываем, сессия стартует по апдейту if( acctStatusType != 1 && acctStatusType != 101 ) { preprocessAccessRequest( request, response, connectionSet ); Integer ipaddr = request.getIntAttribute( 2352, 132, null ); if( ipaddr != null ) { request.setIntAttribute( -1, 8, ipaddr ); } } if ( acctStatusType == 3 || acctStatusType == 2) { // обнуляем общие счетчики трафика (нас интересуют только посервисные) request.setIntAttribute(-1, 42, 0); request.setIntAttribute(-1, 43, 0); } if ( acctStatusType == 103 || acctStatusType == 102) { String sessionID = request.getStringAttribute(-1, 50, null); request.setStringAttribute(-1, 44, sessionID); } if ( acctStatusType == 103 || acctStatusType == 102) { request.setIntAttribute(-1, 40, 3); } } @Override public void preprocessDhcpRequest( DhcpPacket request, DhcpPacket response ) throws Exception { //String userName = Utils.bytesToHexString( remoteId ).substring(4, 16) + ":" + Utils.bytesToHexString( circuitId ).substring(10, 12); try { byte[] circuitId = request.getSubOption( (byte)1 ).value; byte[] remoteId = request.getSubOption( (byte)2 ).value; if (remoteId.length == 8) { byte[] mac = new byte[6]; byte[] port = new byte[1]; System.arraycopy(circuitId, 5, port, 0, 1); System.arraycopy(remoteId, 2, mac, 0, 6); request.setSubOption( (byte)1, port); request.setSubOption( (byte)2, mac); } } catch (java.lang.NullPointerException e) { return; } } }
Класс генерации логина
package ru.clink.bgbilling.module.inet.serv; import ru.bitel.bgbilling.kernel.script.server.dev.EventScript; import ru.bitel.bgbilling.kernel.script.server.dev.EventScriptBase; import ru.bitel.bgbilling.modules.inet.api.common.bean.InetDevice; import ru.bitel.bgbilling.modules.inet.api.common.bean.InetServ; import ru.bitel.bgbilling.modules.inet.api.server.bean.InetDeviceDao; import ru.bitel.bgbilling.modules.inet.api.server.event.InetServChangingEvent; //import ru.bitel.bgbilling.modules.inet.api.server.event.InetServModifiedEvent; import ru.bitel.bgbilling.server.util.Setup; import ru.bitel.common.sql.ConnectionSet; public class ClipsLoginGenerator extends EventScriptBase<InetServChangingEvent> implements EventScript<InetServChangingEvent> { @Override public void onEvent( InetServChangingEvent e, Setup setup, ConnectionSet connectionSet ) throws Exception { InetServ inetServ = e.getInetServ(); int deviceId = inetServ.getDeviceId(); InetDevice device = new InetDeviceDao( connectionSet.getConnection(), e.getModuleId() ).get( deviceId ); // сервис SE с коммутатором if( inetServ.getTypeId() == 1 ) { int port = inetServ.getInterfaceId(); String userName = device.getIdentifier().toLowerCase() + ":" + String.format( "%1$02x", new Object[]{ port } ); inetServ.setLogin( userName ); inetServ.setPassword( "Redback" ); } // сервис SE + GePON if( inetServ.getTypeId() == 2 ) { int vlan = inetServ.getVlan(); String userName = device.getIdentifier().toLowerCase() + ":" + String.format( "%1$04x", new Object[]{ vlan } ); inetServ.setLogin( userName ); inetServ.setPassword( "Redback" ); } } }
Авторизация по VLAN
Для того чтоб была возможность авторизироваться по VLAN+MAC было сделано следующие
конфиг redback
radius.servSearchMode=2 - поиск по vlan на устройстве
dhcp.key.pattern=$deviceId:$mac - чтобы радиус и дхцп привязывались по SE+mac устройства (т.к. в dhcp пакетах не нашел информации о vlan)
radius.agent.option.remoteId.type=0
radius.agent.option.circuitId.type=0 - чтобы SmartEdgeProtocolHandler не искал и не подставлял agentRemoteId (чтобы не искалось дочернее агентское устройство) и agentCircuitId (т.к. vlan подставляем вручную в коде) в радиус пакете
расширен SmartEdgeProtocolHandler - сделано SmartEdgeVlanProtocolHandler, который просто vlanId подставляет
package ru.dinkor.bgbilling.module.inet.dyn.device.redback; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.log4j.Logger; import ru.bitel.bgbilling.kernel.network.radius.RadiusPacket; import ru.bitel.bgbilling.modules.inet.dyn.device.redback.SmartEdgeClipsProtocolHandler; import ru.bitel.bgbilling.modules.inet.radius.InetRadiusProcessor; public class SmartEdgeClipsVlanProtocolHandler extends SmartEdgeClipsProtocolHandler { private static final Logger logger = Logger.getLogger( SmartEdgeClipsVlanProtocolHandler.class ); private final Pattern vlanPattern = Pattern.compile( "(.+) vlan-id (\\d+)" ); @Override protected void setAgentOptions( RadiusPacket request ) { super.setAgentOptions( request ); String nasPortId = request.getStringAttribute( -1, 87, null ); if( nasPortId != null ) { Matcher m = vlanPattern.matcher( nasPortId ); if( m.find() ) { String vlanId = m.group( 2 ); logger.info( "vlanId=" + vlanId ); request.setOption( InetRadiusProcessor.VLAN_ID, vlanId ); } else { logger.error( "vlanId not found!" ); } } else { logger.error( "Attribute Nas-Port-Id not found!" ); } } }