RedBack CLIPS

Материал из BiTel WiKi

(Различия между версиями)
Перейти к: навигация, поиск
(Авторизация по VLAN)
 
(36 промежуточных версий не показаны.)
Строка 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
+
User-Name=f0:7d:68:82:59:27
-
  NAS-Port-Id=2/1 vlan-id 2217 clips 169782
+
NAS-Port-Id=2/1 vlan-id 2217 clips 169782
-
  NAS-Identifier=Redback-IPOE
+
NAS-Identifier=Redback-IPOE
-
  User-Password=Redback
+
User-Password=Redback
-
  NAS-IP-Address=172.16.19.1
+
NAS-IP-Address=172.16.19.1
-
  NAS-Port=553650345
+
NAS-Port=553650345
-
  Service-Type=5
+
Service-Type=5
-
  NAS-Port-Type=5
+
NAS-Port-Type=5
-
  Platform-Type=4
+
Platform-Type=4
-
  Medium-Type=11
+
Medium-Type=11
-
  Agent-Remote-Id={01 06 1C BD B9 E6 48 78}
+
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={3D 3D 07 01 F0 7D 68 82 59 27}
-
  DHCP-Option={0C 0C 07 44 49 52 2D 33 30 30}
+
DHCP-Option={0C 0C 07 44 49 52 2D 33 30 30}
-
  Agent-Circuit-Id={00 04 08 A9 00 01}
+
Agent-Circuit-Id={00 04 08 A9 00 01}
-
  OS-Version=6.5.1.3
+
OS-Version=6.5.1.3
-
  Mac-Addr=f0-7d-68-82-59-27
+
Mac-Addr=f0-7d-68-82-59-27
-
  NAS-Real-Port=553650345
+
NAS-Real-Port=553650345
-
  UNKNOWN[3561--1]={02 0A 01 06 1C BD B9 E6 48 78}
+
UNKNOWN[3561--1]={02 0A 01 06 1C BD B9 E6 48 78}
-
  UNKNOWN[3561--1]={01 08 00 04 08 A9 00 01}
+
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 из этих параметров. Для этого используется динамический класс-обработчик процессора протокола (в биллинге задается в разделе "Типы устройств"), а именно метод preprocessAccessRequest:
+
В качестве логина, SE подставляет MAC-адрес абонента. Параметры DHCP-опции 82 хранятся в атрибутах Agent-Remote-Id и Agent-Circuit-Id. Для того, чтобы авторизовать абонента по MAC-адресу коммутатора и порту, необходимо сгенерировать User-Name из этих параметров. Для этого используется динамический класс-обработчик процессора протокола (в биллинге задается в разделе "Типы устройств"), а метод вызывается в методах обработчика preprocessAccessRequest и preprocessAccountingRequest:
-
 
+
<source lang='java'>
<source lang='java'>
-
    public void preprocessAccessRequest( RadiusPacket request, RadiusPacket response, ConnectionSet connectionSet )
+
/**
-
        throws Exception
+
* Установка 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 = "";
-
// Формируем User-Name формата <mac коммутатора>:<порт коммутатора>
+
-
userName = Utils.bytesToHexString( remoteId ).substring(4, 16) + ":" + Utils.bytesToHexString( circuitId ).substring(10, 12);
+
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-адрес абонента, игнорируем его, в результате чего сессия стартует по первому Update-пакету.
+
После установления сессии, 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-Input-Octets и Acct-Output-Octets для пакетов с Acct-Status-Type=3 и Acct-Status-Type=3.
+
Таким образом, необходимо указывать биллингу, что аккаунтинг посервисный, указав идентификатор родительской сессии, имя сервиса, по которому идет аккаунтинг, и поменять значение Acct-Status-Type на стандартные 1,2,3.
-
У пакетов с Acct-Status-Type=102 и Acct-Status-Type=103 копируем параметр Acct-Multi-Session-Id в параметр Acct-Session-Id и устанавливаем Acct-Status-Type в 3. Таким образом посервисные пакеты выглядят для биллинга как обычные Update'ы.
+
Указанная логика реализуется в методе preprocessAccountingRequest класса SmartEdgeClipsProtocolHandler:
-
 
+
-
Сессия останавливается по общему Stop-пакету с Acct-Session-Type=2.
+
-
 
+
-
Указанная логика реализуется в методе preprocessAccountingRequest класса SmartEdgeProtocolHandler:
+
<source lang='java'>
<source lang='java'>
@Override
@Override
-
    public void preprocessAccountingRequest( RadiusPacket request, RadiusPacket response, ConnectionSet connectionSet )
+
public void preprocessAccountingRequest( RadiusPacket request, RadiusPacket response, ConnectionSet connectionSet )
-
        throws Exception
+
    throws Exception
-
    {
+
{
-
int acctStatusType = request.getIntAttribute( -1, 40, 0 );
+
int acctStatusType = request.getIntAttribute( -1, RadiusDictionary.Acct_Status_Type, -1 );
-
// старты получается не обрабатываем, сессия стартует по апдейту
+
 
-
if( acctStatusType != 1 && acctStatusType != 101 )
+
switch( acctStatusType )
{
{
-
preprocessAccessRequest( request, response, connectionSet );
+
// если сервисный аккаунтинг
 +
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 );
-
Integer ipaddr = request.getIntAttribute( 2352, 132, null );
+
// подменяем Acct-Status-Type, чтобы биллинг понял типы пакетов
-
if( ipaddr != null )
+
request.setIntAttribute( -1, RadiusDictionary.Acct_Status_Type, acctStatusType - 100 );
-
{
+
// устанавливаем id родительской сессии
-
request.setIntAttribute( -1, 8, ipaddr );
+
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;
-
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) {
+
default:
-
request.setIntAttribute(-1, 40, 3);
+
{
 +
// для родительского аккаунтинга устанавилваем состояние по наличию определенных атрибутов
 +
setStateFromAttributes( request );
 +
// устанавливаем поле username
 +
setUsername( request );
 +
}
 +
break;
}
}
-
    }
+
}
</source>
</source>
Строка 295: Строка 331:
<source lang='bash'>
<source lang='bash'>
-
  Acct-Session-Id=0100FFFF7800285F-4E7308EE
+
Acct-Session-Id=0100FFFF7800285F-4E7308EE
-
  Service-Name:1=RSE-SVC-EXT
+
Service-Name:1=RSE-SVC-EXT
-
  Service-Options:1=1
+
Service-Options:1=1
-
  Service-Parameter:1=Rate=48000 Burst=6000000
+
Service-Parameter:1=Rate=48000 Burst=6000000
</source>
</source>
Строка 304: Строка 340:
<source lang='bash'>
<source lang='bash'>
-
  Acct-Interim-Interval=900
+
Acct-Interim-Interval=900
-
  Acct-Session-Id=0100FFFF78002877-4E7308EE
+
Acct-Session-Id=0100FFFF78002877-4E7308EE
-
  Forward-Policy=in:NOAUTH-IPOE
+
Forward-Policy=in:NOAUTH-IPOE
-
  HTTP-Redirect-Profile-Name=NOAUTH
+
HTTP-Redirect-Profile-Name=NOAUTH
</source>
</source>
Строка 316: Строка 352:
Это реализуется в методах connectionModify и connectionClose класса SmartEdgeServiceActivator:
Это реализуется в методах connectionModify и connectionClose класса SmartEdgeServiceActivator:
<source lang='java'>
<source lang='java'>
-
    public Object connectionModify( ServiceActivatorEvent event )
+
@Override
-
        throws Exception
+
public Object connectionModify( ServiceActivatorEvent e )
-
    {
+
        throws Exception
-
log.info( "Connection modify!" );
+
{
-
log.info( "oldState: " + event.getOldState() + "; newState: " + event.getNewState() + "; oldOptionSet: " + event.getOldOptions() + "; newOptionSet: " + event.getNewOptions() );
+
logger.info( "Connection modify: oldState: " + e.getOldState() + "; newState: " + e.getNewState() + "; oldOptionSet: " + e.getOldOptions() + "; newOptionSet: " + e.getNewOptions() );
-
InetConnection connection = event.getConnection();
+
if( e.getNewState() == InetServ.STATE_DISABLE )
 +
{
 +
return connectionClose( e );
 +
}
-
// это Reject-To-Accept коннект, нужно сбросить для инициации нормального коннекта
+
InetConnection connection = e.getConnection();
-
if( event.getOldState() == InetServ.STATE_DISABLE && event.getNewState() == InetServ.STATE_ENABLE )
+
 
 +
if( e.getOldState() == InetServ.STATE_DISABLE )
{
{
// убрать из DHCP, чтобы выдало NaK
// убрать из DHCP, чтобы выдало NaK
EventProcessor.getInstance().request( new InetConnectionManager.ConnectionRemoveEvent( connection ) );
EventProcessor.getInstance().request( new InetConnectionManager.ConnectionRemoveEvent( connection ) );
-
+
 
return null;
return null;
}
}
-
else
 
-
{
 
-
RadiusPacket packet = pod.createModifyRequest();
 
-
packet.addAttributes( serviceCloseAttributes );
 
-
preparePacket( packet, connection );
 
-
log.info( "Send CoA: \n" + packet );
+
// закрываем все сервисы
 +
RadiusPacket request = radiusClient.createModifyRequest();
 +
prepareRequest( request, connection );
 +
request.addAttributes( serviceCloseAttributes );
-
pod.send( packet );
+
logger.info( "Send CoA: \n" + request );
-
packet = pod.createModifyRequest();
+
radiusClient.send( request );
-
+
 
-
preparePacket( packet, connection );
+
request = radiusClient.createModifyRequest();
 +
prepareRequest( request, connection );
-
for( Integer optionId : event.getNewOptions() )
+
// открываем все подключенные сервисы
 +
for( Integer option : e.getNewOptions() )
 +
{
 +
RadiusAttributeSet set = optionRadiusAttributesMap.get( option );
 +
if( set != null )
{
{
-
RadiusAttributeSet attrs = optionSets.get( optionId );
+
request.addAttributes( set );
-
if( attrs != null )
+
-
{
+
-
packet.addAttributes( attrs );
+
-
}
+
}
}
 +
}
-
log.info( "Send CoA: \n" + packet );
+
logger.info( "Send CoA: \n" + request );
-
return pod.sendAsync( packet );
+
return radiusClient.sendAsync( request );
-
}
+
}
-
    }
+
</source>
</source>
<source lang='java'>
<source lang='java'>
-
    public Object connectionClose( ServiceActivatorEvent event )
+
@Override
-
        throws Exception
+
public Object connectionClose( ServiceActivatorEvent event )
-
    {
+
        throws Exception
-
log.info( "Connection close!" );
+
{
 +
logger.info( "Connection close" );
InetConnection connection = event.getConnection();
InetConnection connection = event.getConnection();
-
RadiusPacket packet = pod.createModifyRequest();
+
RadiusPacket request = radiusClient.createModifyRequest();
-
+
-
preparePacket( packet, connection );
+
-
packet.addAttributes( lockAttributes );
+
prepareRequest( request, connection );
 +
 
 +
request.addAttributes( disableRadiusAttributes );
// убрать из DHCP, чтобы выдало NaK
// убрать из DHCP, чтобы выдало NaK
EventProcessor.getInstance().request( new InetConnectionManager.ConnectionRemoveEvent( connection ) );
EventProcessor.getInstance().request( new InetConnectionManager.ConnectionRemoveEvent( connection ) );
-
log.info( "Send CoA lock: \n" + packet );
+
logger.info( "Send CoA lock: \n" + request );
-
return pod.sendAsync( packet );
+
return radiusClient.sendAsync( request );
-
    }
+
}
</source>
</source>
-
== Настройка биллинга ==
+
== Настройка модуля Inet ==
Устанавливаем модуль Inet стандартным способом.
Устанавливаем модуль Inet стандартным способом.
Строка 399: Строка 439:
         <param name="db.driver" value="com.mysql.jdbc.Driver"/>
         <param name="db.driver" value="com.mysql.jdbc.Driver"/>
-
         <param name="db.url" value="jdbc:mysql://db.clink.ru/bgbilling?useUnicode=true&amp;characterEncoding=Cp1251&amp;allowUrlInLocalInfile=true&amp;zeroDateTimeBehavior=convertToNull&amp;jdbcCompliantTruncation=false&amp;queryTimeoutKillsConnection=true"/>
+
         <param name="db.url" value="jdbc:mysql://db.provider.ru/bgbilling?useUnicode=true&amp;characterEncoding=Cp1251&amp;allowUrlInLocalInfile=true&amp;zeroDateTimeBehavior=convertToNull&amp;jdbcCompliantTruncation=false&amp;queryTimeoutKillsConnection=true"/>
         <param name="db.user" value="bgbilling"/>
         <param name="db.user" value="bgbilling"/>
-
         <param name="db.pswd" value="9muDwtZIBye8"/>
+
         <param name="db.pswd" value="xxxxxx"/>
         <param name="mq.url" value="failover:(tcp://localhost:61616)"/>
         <param name="mq.url" value="failover:(tcp://localhost:61616)"/>
         <param name="mq.user" value="bill"/>
         <param name="mq.user" value="bill"/>
-
         <param name="mq.pswd" value="y4LtRThdoIko"/>
+
         <param name="mq.pswd" value="xxxxxx"/>
         <param name="rootDeviceId" value="3"/>
         <param name="rootDeviceId" value="3"/>
Строка 430: Строка 470:
                 <bean name="radiusListener" class="ru.bitel.bgbilling.modules.inet.radius.InetRadiusListener">
                 <bean name="radiusListener" class="ru.bitel.bgbilling.modules.inet.radius.InetRadiusListener">
                         <constructor>
                         <constructor>
-
                                 <param name="host" value="194.165.18.10"/>
+
                                 <param name="host" value="194.xx.xx.10"/>
                                 <param name="port" value="11812"/>
                                 <param name="port" value="11812"/>
                                 <param name="byteBufferCapacity">512 * 1024</param>
                                 <param name="byteBufferCapacity">512 * 1024</param>
Строка 447: Строка 487:
                 <bean name="dhcpListener" class="ru.bitel.bgbilling.kernel.network.dhcp.DhcpListener">
                 <bean name="dhcpListener" class="ru.bitel.bgbilling.kernel.network.dhcp.DhcpListener">
                         <constructor>
                         <constructor>
-
                                 <param name="host" value="194.165.18.10"/>
+
                                 <param name="host" value="194.xx.xx.10"/>
                                 <param name="port" value="67"/>                                                     
                                 <param name="port" value="67"/>                                                     
                                 <param name="byteBufferCapacity">512 * 1024</param>
                                 <param name="byteBufferCapacity">512 * 1024</param>
Строка 472: Строка 512:
         <param name="db.driver" value="com.mysql.jdbc.Driver"/>
         <param name="db.driver" value="com.mysql.jdbc.Driver"/>
-
         <param name="db.url" value="jdbc:mysql://db.clink.ru/bgbilling?useUnicode=true&amp;characterEncoding=Cp1251&amp;allowUrlInLocalInfile=true&amp;zeroDateTimeBehavior=convertToNull&amp;jdbcCompliantTruncation=false&amp;queryTimeoutKillsConnection=true"/>
+
         <param name="db.url" value="jdbc:mysql://db.provider.ru/bgbilling?useUnicode=true&amp;characterEncoding=Cp1251&amp;allowUrlInLocalInfile=true&amp;zeroDateTimeBehavior=convertToNull&amp;jdbcCompliantTruncation=false&amp;queryTimeoutKillsConnection=true"/>
         <param name="db.user" value="bgbilling"/>
         <param name="db.user" value="bgbilling"/>
-
         <param name="db.pswd" value="9muDwtZIBye8"/>
+
         <param name="db.pswd" value="xxxxxx"/>
         <param name="mq.url" value="failover:(tcp://localhost:61616)"/>
         <param name="mq.url" value="failover:(tcp://localhost:61616)"/>
         <param name="mq.user" value="bill"/>
         <param name="mq.user" value="bill"/>
-
         <param name="mq.pswd" value="y4LtRThdoIko"/>
+
         <param name="mq.pswd" value="xxxxxx"/>
         <param name="rootDeviceId" value="3"/>
         <param name="rootDeviceId" value="3"/>
Строка 500: Строка 540:
                 <bean name="radiusListener" class="ru.bitel.bgbilling.modules.inet.radius.InetRadiusListener">
                 <bean name="radiusListener" class="ru.bitel.bgbilling.modules.inet.radius.InetRadiusListener">
                         <constructor>
                         <constructor>
-
                                 <param name="host" value="194.165.18.10"/>
+
                                 <param name="host" value="194.xx.xx.10"/>
                                 <param name="port" value="11813"/>
                                 <param name="port" value="11813"/>
                                 <param name="byteBufferCapacity">512 * 1024</param>
                                 <param name="byteBufferCapacity">512 * 1024</param>
Строка 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.
-
[[Изображение:Rb_clips_inet_0.png|thumb|600px|Привязка типов трафика]]
+
{|
 +
|- valign=top
 +
| [[Изображение:Rb_clips_inet_0.png|thumb|600px|Привязка типов трафика]]  
 +
|}
 +
 
 +
В разделе Устройства и ресурсы нужно добавить две категории IP-адресов: одну для работающих абонентов, другую - для отключенных. Важно: конфигурации нужно указывать код категории ресурсов, а не код диапазона.
 +
{|
 +
|- valign=top
 +
| [[Изображение:Rb_clips_inet_4.png|thumb|600px|IP ресурсы]]
 +
|}
 +
 
 +
Далее во подразделе "Устройства и ресурсы", нам  необходимо добавить два типа устройства: сам RedBack и абонентский коммутатор, например DES-3526. Для RedBack в качестве обработчика активации сервисов указываем нужный SmartEdgeServiceActivator, а в качестве обработчика процессора протокола - SmartEdgeProtocolHandler. (см. раздел "Исходный код"). Для коммутатора во вкладке "Интерфейсы", добавляем необходимое количество интерфейсов (в нашем случае - 24 порта).
 +
Также необходимо добавить тип устройства "Process group", ничего в нем не настраивая.
 +
{|
 +
|- valign=top
 +
| [[Изображение:Rb_clips_inet_1.png|thumb|300px|Типы устройства - RedBack]]
 +
| [[Изображение: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&amp;characterEncoding=Cp1251&amp;allowUrlInLocalInfile=true&amp;zeroDateTimeBehavior=convertToNull&amp;jdbcCompliantTruncation=false&amp;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&amp;characterEncoding=Cp1251&amp;allowUrlInLocalInfile=true&amp;zeroDateTimeBehavior=convertToNull&amp;jdbcCompliantTruncation=false&amp;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-адресов: одну для работающих абонентов, другую - для отключенных. Важно: конфигурации нужно указывать код категории ресурсов, а не код диапазона.

IP ресурсы

Далее во подразделе "Устройства и ресурсы", нам необходимо добавить два типа устройства: сам RedBack и абонентский коммутатор, например DES-3526. Для RedBack в качестве обработчика активации сервисов указываем нужный SmartEdgeServiceActivator, а в качестве обработчика процессора протокола - SmartEdgeProtocolHandler. (см. раздел "Исходный код"). Для коммутатора во вкладке "Интерфейсы", добавляем необходимое количество интерфейсов (в нашем случае - 24 порта). Также необходимо добавить тип устройства "Process group", ничего в нем не настраивая.

Типы устройства - RedBack
Типы устройства - коммутатор

Во вкладке "Устройства", необходимо добавить реальные устройства, используемые в сети в соответствии с логической иерархией.

На самом верхнем уровне добавляется устройство с типом 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!" );
		}
	}
}


Ссылки

  1. Обсуждение на форуме
Личные инструменты