You are looking at the HTML representation of the XML format.
HTML is good for debugging, but probably is not suitable for your application.
See complete documentation, or API help for more information.
<?xml version="1.0"?>
<api>
  <query-continue>
    <allpages gapfrom="SMS рассылка через SMPP по средствам дин кода в 5.2" />
  </query-continue>
  <query>
    <pages>
      <page pageid="562" ns="0" title="RedBack CLIPS">
        <revisions>
          <rev xml:space="preserve">CLIPS - это технология, используемая в оборудовании фирмы Ericsson для аутентификации, авторизации и аккаунтинга абонентов IPOE. Принципы CLIPS подробно описаны в документе &quot;Clips HOWTO&quot; ( 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):

&lt;source lang='bash'&gt;
# dhcp_local_relay позволяет вставлять в приходящие от абонентов DHCP-запросы опцию 82.
enable dhcp_local_relay
config dhcp_local_relay vlanid 2000 state enable
# 2000 - вилан, настроенный на абонентских портах
&lt;/source&gt;

Конфиг SE100:

&lt;source lang='bash'&gt;
context IPOE
! 
 no ip domain-lookup
!
# Интерфейс, к которому будут &quot;биндиться&quot; абоненты после авторизации
 interface CLIENTS multibind
  ip address 193.x.x.1/24
  dhcp proxy 65535
  ip arp secured-arp
!
# Интерфейс, к которому будут &quot;биндиться&quot; абоненты, не прошедшие авторизацию (аналог 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 &quot;http://noauth.provider.ru/&quot;
# Настройки 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 &quot;ip in forward class EXTERNAL qos&quot;
  seq 20 attribute Dynamic-Policy-Filter &quot;ip out forward class EXTERNAL qos&quot;
  seq 30 attribute Dynamic-Qos-Parameter &quot;meter-class-rate EXTERNAL rate-absolute $Rate&quot;
  seq 40 attribute Dynamic-Qos-Parameter &quot;meter-class-burst EXTERNAL $Burst&quot;
  seq 50 attribute Dynamic-Qos-Parameter &quot;police-class-rate EXTERNAL rate-absolute $Rate&quot;
  seq 60 attribute Dynamic-Qos-Parameter &quot;police-class-burst EXTERNAL $Burst&quot;
  seq 70 attribute Service-Interim-Accounting 900
!
 ip route 0.0.0.0/0 context local
!
!
# Адрес DHCP-сервера
 dhcp relay server 194.165.18.17
&lt;/source&gt;

После получения DHCP DISCOVER запроса, RedBack преобразует параметры запроса в Radius-атрибуты и отправляет Access-Request на указанный Radius-сервер. Запрос выглядит примерно следующим образом:

&lt;source lang='bash'&gt;
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}
&lt;/source&gt;

В качестве Radius-сервера используется приложение BGInetAccess.
В качестве логина, SE подставляет MAC-адрес абонента. Параметры DHCP-опции 82 хранятся в атрибутах Agent-Remote-Id и Agent-Circuit-Id. Для того, чтобы авторизовать абонента по MAC-адресу коммутатора и порту, необходимо сгенерировать User-Name из этих параметров. Для этого используется динамический класс-обработчик процессора протокола (в биллинге задается в разделе &quot;Типы устройств&quot;), а метод вызывается в методах обработчика preprocessAccessRequest и preprocessAccountingRequest:
&lt;source lang='java'&gt;
	/**
	 * Установка 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 &amp;&amp; remoteId != null &amp;&amp; circuitId != null )
		{
			String callingStation = macAddr.replaceAll( &quot;\\-&quot;, &quot;&quot; );
			
			String userName = &quot;&quot;;
			
			if (remoteId.length == 8) {
				logger.info( &quot;Format of Option 82 is D-link&quot; );
				userName = Utils.bytesToHexString( remoteId ).substring(4, 16) + &quot;:&quot; + Utils.bytesToHexString( circuitId ).substring(10, 12);
			}
			
			if (remoteId.length == 11) {
				logger.info( &quot;Format of Option 82 is Eltex&quot; );
				//userName = Utils.bytesToHexString( remoteId ).substring(10, 22) + Utils.bytesToHexString( circuitId ).substring(4, 8) + &quot;:&quot; + Utils.bytesToHexString( circuitId ).substring(20, 22);
				userName = Utils.bytesToHexString( remoteId ).substring(10, 22) + &quot;:&quot; + Utils.bytesToHexString( circuitId ).substring(8, 12);
			}
			userName = userName.toLowerCase();
			request.setStringAttribute( -1, 1, userName );
			request.setStringAttribute( -1, 31, callingStation );
		}
	}
&lt;/source&gt;

При успешной аутентификации, BGInetAccess отправляет Access-Accept следующего вида:

&lt;source lang='bash'&gt;
  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-интерфейсу &quot;привязать&quot; абонента
  IP-Interface-Name=CLIENTS
&lt;/source&gt;

Для получения адреса по DHCP, в Radius ответе не должно быть атрибута Framed-IP-Address. Поэтому используется следующий метод для обработки ответа:

&lt;source lang='java'&gt;
    public void postprocessAccessRequest( RadiusPacket request, RadiusPacket response, ConnectionSet connectionSet )
        throws Exception
    {
		response.removeAttributes( -1, 8 );		
    }
&lt;/source&gt;

После успешной авторизации, SE посылает запрос на DHCP сервер. В качестве сервера DHCP используется то же самое приложение BGInetAccess. Для успешной привязки DHCP-запроса к сессии, необходимо предобработать его в соответствии с выбранным алгоритмом:

&lt;source lang='java'&gt;
	@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;
		}
    }
&lt;/source&gt;

После этого абонент получает адрес и начинает работать.

== Процесс аккаунтинга ==

После установления сессии, 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:
&lt;source lang='java'&gt;
	@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( &quot;parentAcctSessionId=&quot; + parentAcctSessionId + &quot;, serviceName=&quot; + 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 ) &amp;&amp; disableServiceName.equals( serviceName ) )
				{
					if( acctStatusType == 101 || acctStatusType == 102 )
					{
						logger.debug( &quot;State is disable (from start disable service)&quot; );
						request.setOption( InetRadiusProcessor.DEVICE_STATE, InetServ.STATE_DISABLE );
					}
					else
					{
						logger.debug( &quot;State is enable (from stop disable service)&quot; );
						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;
		}
	}
&lt;/source&gt;

== Управление сессиями ==

Для смены параметров подключения абонента, используются запросы CoA. В нашем случае осуществляется либо изменение скорости соединения, либо отключение абонента.

В случае изменения скорости, необходимо отправить на Smart Edge два запроса: сначала отключить сервис, а затем тут же включить с новыми параметрами.

&lt;source lang='bash'&gt;
Acct-Session-Id=0100FFFF7800285F-4E7308EE
Deactivate-Service-Name:1=RSE-SVC-EXT
&lt;/source&gt;


&lt;source lang='bash'&gt;
Acct-Session-Id=0100FFFF7800285F-4E7308EE
Service-Name:1=RSE-SVC-EXT
Service-Options:1=1
Service-Parameter:1=Rate=48000 Burst=6000000
&lt;/source&gt;

При отключении абонента по балансу, через CoA абонентской сессии устанавливаются параметры редиректа на страницу с ошибкой. Запрос выглядит следующим образом:

&lt;source lang='bash'&gt;
Acct-Interim-Interval=900
Acct-Session-Id=0100FFFF78002877-4E7308EE
Forward-Policy=in:NOAUTH-IPOE
HTTP-Redirect-Profile-Name=NOAUTH
&lt;/source&gt;

Для того, чтобы пользователь получил адрес из &quot;серой&quot; сети, ему выдается DHCP NAK.

При включении абонента, ему также выдается DHCP NAK чтобы инициализировать получение нового адреса.

Это реализуется в методах connectionModify и connectionClose класса SmartEdgeServiceActivator:
&lt;source lang='java'&gt;
@Override
	public Object connectionModify( ServiceActivatorEvent e )
	        throws Exception
	{
		logger.info( &quot;Connection modify: oldState: &quot; + e.getOldState() + &quot;; newState: &quot; + e.getNewState() + &quot;; oldOptionSet: &quot; + e.getOldOptions() + &quot;; newOptionSet: &quot; + 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( &quot;Send CoA: \n&quot; + 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( &quot;Send CoA: \n&quot; + request );

		return radiusClient.sendAsync( request );
	}
&lt;/source&gt;
&lt;source lang='java'&gt;
	@Override
	public Object connectionClose( ServiceActivatorEvent event )
	        throws Exception
	{
		logger.info( &quot;Connection close&quot; );

		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( &quot;Send CoA lock: \n&quot; + request );

		return radiusClient.sendAsync( request );
	}
&lt;/source&gt;

== Настройка модуля Inet ==

Устанавливаем модуль Inet стандартным способом.

Скачиваем и устанавливаем приложения BGInetAccess и BGInetAccounting.

Конфигурация BGInetAccess (inet-access.xml):
&lt;source lang='xml'&gt;
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;application context=&quot;access&quot;&gt;
        &lt;param name=&quot;app.name&quot; value=&quot;BGInetAccess&quot;/&gt;
        &lt;param name=&quot;app.id&quot; value=&quot;1&quot;/&gt;

        &lt;param name=&quot;moduleId&quot; value=&quot;15&quot;/&gt;

        &lt;param name=&quot;db.driver&quot; value=&quot;com.mysql.jdbc.Driver&quot;/&gt;
        &lt;param name=&quot;db.url&quot; value=&quot;jdbc:mysql://db.provider.ru/bgbilling?useUnicode=true&amp;amp;characterEncoding=Cp1251&amp;amp;allowUrlInLocalInfile=true&amp;amp;zeroDateTimeBehavior=convertToNull&amp;amp;jdbcCompliantTruncation=false&amp;amp;queryTimeoutKillsConnection=true&quot;/&gt;
        &lt;param name=&quot;db.user&quot; value=&quot;bgbilling&quot;/&gt;
        &lt;param name=&quot;db.pswd&quot; value=&quot;xxxxxx&quot;/&gt;

        &lt;param name=&quot;mq.url&quot; value=&quot;failover:(tcp://localhost:61616)&quot;/&gt;
        &lt;param name=&quot;mq.user&quot; value=&quot;bill&quot;/&gt;
        &lt;param name=&quot;mq.pswd&quot; value=&quot;xxxxxx&quot;/&gt;

        &lt;param name=&quot;rootDeviceId&quot; value=&quot;3&quot;/&gt;
        &lt;!--  типы фейковых устройств, являющихся аккаунтинг серверами --&gt;
        &lt;param name=&quot;accounting.deviceTypeIds&quot; value=&quot;3&quot;/&gt;

        &lt;param name=&quot;commonIdentifierName&quot; value=&quot;rootDeviceId&quot; /&gt;

        &lt;bean name=&quot;access&quot; class=&quot;ru.bitel.bgbilling.modules.inet.access.Access&quot; /&gt;

        &lt;param name=&quot;datalog.radius.dir&quot; value=&quot;data/radius&quot;/&gt;
        &lt;param name=&quot;datalog.dhcp.dir&quot; value=&quot;data/dhcp&quot; /&gt;


        &lt;context name=&quot;radius&quot;&gt;
                &lt;bean name=&quot;radiusProcessor&quot; class=&quot;ru.bitel.bgbilling.modules.inet.radius.InetRadiusProcessor&quot;/&gt;
                
                &lt;scheduledExecutorService name=&quot;hrlydtlggr&quot; corePoolSize=&quot;1&quot; /&gt;
                
                &lt;bean name=&quot;radiusDataLogger&quot; class=&quot;ru.bitel.bgbilling.modules.inet.radius.RadiusHourlyDataLogger&quot;&gt;
                        &lt;param name=&quot;scheduledExecutor&quot;&gt;hrlydtlggr&lt;/param&gt;
                &lt;/bean&gt;
                
                &lt;bean name=&quot;radiusListener&quot; class=&quot;ru.bitel.bgbilling.modules.inet.radius.InetRadiusListener&quot;&gt;
                        &lt;constructor&gt;
                                &lt;param name=&quot;host&quot; value=&quot;194.xx.xx.10&quot;/&gt;
                                &lt;param name=&quot;port&quot; value=&quot;11812&quot;/&gt;
                                &lt;param name=&quot;byteBufferCapacity&quot;&gt;512 * 1024&lt;/param&gt;
                                &lt;param name=&quot;processor&quot;&gt;radiusProcessor&lt;/param&gt;
                                &lt;param name=&quot;mode&quot;&gt;RadiusListener.Mode.authentication&lt;/param&gt;
                                &lt;param name=&quot;dataLogger&quot;&gt;radiusDataLogger&lt;/param&gt;
                                &lt;param name=&quot;threadCount&quot;&gt;10&lt;/param&gt;
                                &lt;param name=&quot;maxQueueSize&quot;&gt;200&lt;/param&gt;
                        &lt;/constructor&gt;
                &lt;/bean&gt;
        &lt;/context&gt;

        &lt;context name=&quot;dhcp&quot;&gt;
                &lt;bean name=&quot;dhcpProcessor&quot; class=&quot;ru.bitel.bgbilling.modules.inet.dhcp.InetDhcpHelperProcessor&quot;/&gt;
                
                &lt;bean name=&quot;dhcpListener&quot; class=&quot;ru.bitel.bgbilling.kernel.network.dhcp.DhcpListener&quot;&gt;
                        &lt;constructor&gt;
                                &lt;param name=&quot;host&quot; value=&quot;194.xx.xx.10&quot;/&gt;
                                &lt;param name=&quot;port&quot; value=&quot;67&quot;/&gt;                                                     
                                &lt;param name=&quot;byteBufferCapacity&quot;&gt;512 * 1024&lt;/param&gt;
                                &lt;param name=&quot;processor&quot;&gt;dhcpProcessor&lt;/param&gt;
                                &lt;param name=&quot;dataLogger&quot;&gt;&lt;/param&gt;
                                &lt;param name=&quot;threadCount&quot;&gt;10&lt;/param&gt;
                                &lt;param name=&quot;maxQueueSize&quot;&gt;200&lt;/param&gt;
                        &lt;/constructor&gt;
                &lt;/bean&gt;
        &lt;/context&gt;

&lt;/application&gt;
&lt;/source&gt;


Конфигурация BGInetAccounting (inet-accounting.xml):
&lt;source lang='java'&gt;
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;application context=&quot;accounting&quot;&gt;
        &lt;param name=&quot;app.name&quot; value=&quot;BGInetAccounting&quot;/&gt;
        &lt;param name=&quot;app.id&quot; value=&quot;2&quot;/&gt;

        &lt;param name=&quot;moduleId&quot; value=&quot;15&quot;/&gt;

        &lt;param name=&quot;db.driver&quot; value=&quot;com.mysql.jdbc.Driver&quot;/&gt;
        &lt;param name=&quot;db.url&quot; value=&quot;jdbc:mysql://db.provider.ru/bgbilling?useUnicode=true&amp;amp;characterEncoding=Cp1251&amp;amp;allowUrlInLocalInfile=true&amp;amp;zeroDateTimeBehavior=convertToNull&amp;amp;jdbcCompliantTruncation=false&amp;amp;queryTimeoutKillsConnection=true&quot;/&gt;
        &lt;param name=&quot;db.user&quot; value=&quot;bgbilling&quot;/&gt;
        &lt;param name=&quot;db.pswd&quot; value=&quot;xxxxxx&quot;/&gt;

        &lt;param name=&quot;mq.url&quot; value=&quot;failover:(tcp://localhost:61616)&quot;/&gt;
        &lt;param name=&quot;mq.user&quot; value=&quot;bill&quot;/&gt;
        &lt;param name=&quot;mq.pswd&quot; value=&quot;xxxxxx&quot;/&gt;

        &lt;param name=&quot;rootDeviceId&quot; value=&quot;3&quot;/&gt;

        &lt;param name=&quot;commonIdentifierName&quot; value=&quot;rootDeviceId&quot;/&gt;

        &lt;bean name=&quot;accounting&quot; class=&quot;ru.bitel.bgbilling.modules.inet.accounting.Accounting&quot;/&gt;

        &lt;param name=&quot;datalog.radius.dir&quot; value=&quot;data/radius&quot; /&gt;
        &lt;param name=&quot;datalog.flow.dir&quot; value=&quot;data/flow&quot; /&gt;

        &lt;context name=&quot;radius&quot;&gt;
                &lt;bean name=&quot;processor&quot; class=&quot;ru.bitel.bgbilling.modules.inet.radius.InetRadiusProcessor&quot;/&gt;
                
                &lt;scheduledExecutorService name=&quot;hrlydtlggr&quot; corePoolSize=&quot;1&quot;/&gt;
                
                &lt;bean name=&quot;radiusDataLogger&quot; class=&quot;ru.bitel.bgbilling.modules.inet.radius.RadiusHourlyDataLogger&quot;&gt;
                        &lt;param name=&quot;scheduledExecutor&quot;&gt;hrlydtlggr&lt;/param&gt;
                &lt;/bean&gt;
                
                &lt;bean name=&quot;radiusListener&quot; class=&quot;ru.bitel.bgbilling.modules.inet.radius.InetRadiusListener&quot;&gt;
                        &lt;constructor&gt;
                                &lt;param name=&quot;host&quot; value=&quot;194.xx.xx.10&quot;/&gt;
                                &lt;param name=&quot;port&quot; value=&quot;11813&quot;/&gt;
                                &lt;param name=&quot;byteBufferCapacity&quot;&gt;512 * 1024&lt;/param&gt;
                                &lt;param name=&quot;processor&quot;&gt;processor&lt;/param&gt;
                                &lt;param name=&quot;mode&quot;&gt;RadiusListener.Mode.accounting&lt;/param&gt;
                                &lt;param name=&quot;setup&quot;&gt;setup&lt;/param&gt;
                                &lt;param name=&quot;dataLogger&quot;&gt;radiusDataLogger&lt;/param&gt;
                        &lt;/constructor&gt;
                &lt;/bean&gt;
        &lt;/context&gt;
&lt;/application&gt;
&lt;/source&gt;

Заходим во вкладку &quot;Опции&quot; модуля. В нашем случае каждая опция отвечает за определенную скорость доступа. Сами параметры скорости указываются далее, в конфигурации устройств. Здесь необходимо лишь добавить нужное количество опций с удобными для восприятия названиями (например &quot;10Mbit&quot;).

В разделе &quot;Трафик&quot; в подразделе &quot;Типы трафика&quot; нужно добавить три вида трафика: Время, внешний входящий трафик, внешний исходящий трафик.
В подразделе &quot;Привязка типов трафика&quot; добавляем привязку, с двумя элементами для входящего и исходящего трафиков. Тип элементов - Radius. ServiceName - RSE-SVC-EXT - таким образом значения трафика будут получаться только из сервисной сессии RSE-SVC-EXT (для идентификации имени сервиса в обработчике протокола в предобработке устанавливается имя сервиса request.setOption( InetRadiusProcessor.SERVICE_NAME, serviceName );).  Код вендора -2, код элемента 2 и 1 соответственно для входящего и исходящего трафиков. Таком образом мы указываем, что трафик нужно брать из атрибутов Acct-Output-Octets, Acct-Output-Gigawords, Acct-Input-Octets и Acct-Input-Gigawords.
{|
|- valign=top
| [[Изображение:Rb_clips_inet_0.png|thumb|600px|Привязка типов трафика]] 
|}

В разделе Устройства и ресурсы нужно добавить две категории IP-адресов: одну для работающих абонентов, другую - для отключенных. Важно: конфигурации нужно указывать код категории ресурсов, а не код диапазона.
{|
|- valign=top
| [[Изображение:Rb_clips_inet_4.png|thumb|600px|IP ресурсы]] 
|}

Далее во подразделе &quot;Устройства и ресурсы&quot;, нам  необходимо добавить два типа устройства: сам RedBack и абонентский коммутатор, например DES-3526. Для RedBack в качестве обработчика активации сервисов указываем нужный SmartEdgeServiceActivator, а в качестве обработчика процессора протокола - SmartEdgeProtocolHandler. (см. раздел &quot;Исходный код&quot;). Для коммутатора во вкладке &quot;Интерфейсы&quot;, добавляем необходимое количество интерфейсов (в нашем случае - 24 порта).
Также необходимо добавить тип устройства &quot;Process group&quot;, ничего в нем не настраивая.
{|
|- valign=top
| [[Изображение:Rb_clips_inet_1.png|thumb|300px|Типы устройства - RedBack]] 
| [[Изображение:Rb_clips_inet_2.png|thumb|300px|Типы устройства - коммутатор]] 
|}

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

На самом верхнем уровне добавляется устройство с типом ProcessGroup со следующей конфигурацией:
&lt;source lang='bash'&gt;
#типы устройств - 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
&lt;/source&gt;

Уровнем ниже добавляем устройство типа RedBack, то есть наш NAS. Указываем IP-адрес (соответствует атрибуту Nas-Ip-Address), идентификатор (соответствует атрибуту NAS-Identifier) и секрет радиуса. Конфигурация устройства:
&lt;source lang='bash'&gt;
#хост для отправки PoD и CoA запросов (по умолчанию - хост, заданный в параметрах устройства Хост/порт)
#radius.host=&lt;хост устройства&gt;
#порт для отправки PoD и CoA запросов (по умолчанию - порт, заданный в параметрах устройства Хост/порт)
#radius.port=&lt;порт устройства&gt;
#идентификатор - Nas-Identifier (по умолчанию - значение из поля Идентификатор параметров устройства)
#radius.identifier=&lt;идентификатор устройства&gt;
#используемый secret для общения по radius-протоколу (по умолчанию - значение из поля Community/secret параметров устройства)
#radius.secret=&lt;community/sercret устройства&gt;

# Атрибуты, которые передаются для всех сессий.
# IP-Interface-Name соответствует интерфейсу в конфиге RedBack, к которому &quot;биндится&quot; абонентская сессия (обязательный параметр).
# 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 адресов из ресурсов, из которых будут выдаваться адреса (&quot;пул&quot;, указывается во вкладке &quot;IP ресурсы&quot;)
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 адресов из ресурсов, из которых будут выдаваться адреса для отключенных (&quot;пул&quot;, указывается во вкладке &quot;IP ресурсы&quot;)
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=
&lt;/source&gt;

На уровень ниже RedBack добавляем устройство с типом DES-3526. Единственное, что нужно там указать - это идентификатор, который соответствует MAC-адресу коммутатора без разделителей.
{|
|- valign=top
| [[Изображение:Rb_clips_inet_3.png|thumb|600px|Иерархия устройств]] 
|}


Далее необходимо создать тип сервиса во вкладке &quot;Типы сервисов&quot;. Сервис - это услуга, которая привязывается к договору. В настройках типа сервиса указывается, какие поля будут доступны при добавлении/редактировании сервиса на договоре. В нашем случае это устройство (коммутатор) и интерфейс (порт коммутатора).
{|
|- valign=top
| [[Изображение:Rb_clips_inet_5.png|thumb|600px|Тип сервиса]] 
|}

Поскольку аутентификация фактически происходит по логину, нам нужно чтобы при сохранении сервиса, логин генерировался исходя из идентификатора коммутатора и номера порта. Для этого служит динамический класс, привязываемый к событию &quot;изменяется сервис договора&quot;.
&lt;source lang='java'&gt;
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&lt;InetServChangingEvent&gt;
	implements EventScript&lt;InetServChangingEvent&gt; {
	@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() + &quot;:&quot; + String.format( &quot;%1$02x&quot;, new Object[]{ port } );
			
			inetServ.setLogin( userName );
			inetServ.setPassword( &quot;Redback&quot; );
		}
	}
}
&lt;/source&gt;

== Настройка тарифных планов ==
Для примера создаем два тарифных плана. Первый - безлимитный, с различной скоростью в разное время суток (устанавливается опциями). Второй - с учетом трафика.

{|
|- 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'''


Класс активации сервиса

&lt;source lang='java'&gt;
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&lt;Integer, RadiusAttributeSet&gt; optionSets;
	
	private RadiusAttributeSet serviceCloseAttributes;

	@Override
    public Object init( Setup setup, int moduleId, InetDevice device, InetDeviceType deviceType, ParameterMap deviceParams )
        throws Exception
    {
		String nasHost = deviceParams.get( &quot;nas.radius.host&quot;, device.getHost() );
		InetAddress nasHostAddr = InetAddress.getByName( nasHost );
		int nasPort = deviceParams.getInt( &quot;nas.radius.port&quot;, 1700 );

		byte[] nasSecret = deviceParams.get( &quot;nas.secret&quot;, device.getSecret() ).getBytes();

		pod = new PodSupport( nasHostAddr, nasPort, nasSecret );

		log.info( &quot;Init script for device: &quot; + device.getId() );

		// атрибуты отправляются в CoA при необходимости сброса
		lockAttributes = RadiusAttributeSet.newRadiusAttributeSet( deviceParams.get( &quot;redirect.attributes&quot;, &quot;&quot; ) );

		// RADIUS атрибуты по ключу опции
		optionSets = RadiusAttributeSet.newRadiusAttributeSetMap( deviceParams, &quot;option.&quot;, &quot;attributes&quot; );
		
		serviceCloseAttributes = RadiusAttributeSet.newRadiusAttributeSet( deviceParams.get( &quot;close.attributes&quot;, &quot;Deactivate-Service-Name:1=RSE-SVC-EXT&quot; ) );

		log.info( &quot;Options map size: &quot; + optionSets.size() );
		
		return null;		
    }

	@Override
    public Object destroy()
        throws Exception
    {
		pod.destroy();
		return null;
    }

	@Override
    public Object connectionClose( ServiceActivatorEvent event )
        throws Exception
    {
		log.info( &quot;Connection close!&quot; );

		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( &quot;Send CoA lock: \n&quot; + packet );

		return pod.sendAsync( packet );
    }
	
	@Override
    public Object connectionModify( ServiceActivatorEvent event )
        throws Exception
    {
		log.info( &quot;Connection modify!&quot; );
		log.info( &quot;oldState: &quot; + event.getOldState() + &quot;; newState: &quot; + event.getNewState() + &quot;; oldOptionSet: &quot; + event.getOldOptions() + &quot;; newOptionSet: &quot; + event.getNewOptions() );

		InetConnection connection = event.getConnection();

		// это Reject-To-Accept коннект, нужно сбросить для инициации нормального коннекта
		if( event.getOldState() == InetServ.STATE_DISABLE &amp;&amp; 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( &quot;Send CoA: \n&quot; + 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( &quot;Send CoA: \n&quot; + packet );

			return pod.sendAsync( packet );	
		}
    }
	
	private void preparePacket( RadiusPacket packet, InetConnection connection )
	{
		packet.setStringAttribute( -1, RadiusDictionary.Acct_Session_Id, connection.getAcctSessionId() );
	}	
}
&lt;/source&gt;

Класс обработчика процессора протокола

&lt;source lang='java'&gt;
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 &amp;&amp; remoteId != null &amp;&amp; circuitId != null )
		{
			String callingStation = macAddr.replaceAll( &quot;\\-&quot;, &quot;&quot; );
			
			String userName = &quot;&quot;;
			
			if (remoteId.length == 8) {
				log.info( &quot;Format of Option 82 is D-link&quot; );
				userName = Utils.bytesToHexString( remoteId ).substring(4, 16) + &quot;:&quot; + 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 &amp;&amp; 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) + &quot;:&quot; + 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;
		}
    }
}

&lt;/source&gt;

Класс генерации логина

&lt;source lang='java'&gt;
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&lt;InetServChangingEvent&gt;
	implements EventScript&lt;InetServChangingEvent&gt; {
	@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() + &quot;:&quot; + String.format( &quot;%1$02x&quot;, new Object[]{ port } );
			
			inetServ.setLogin( userName );
			inetServ.setPassword( &quot;Redback&quot; );
		}
		
		// сервис SE + GePON
		if( inetServ.getTypeId() ==  2 )
		{
			int vlan = inetServ.getVlan();

			String userName = device.getIdentifier().toLowerCase() + &quot;:&quot; + String.format( &quot;%1$04x&quot;, new Object[]{ vlan } );
			
			inetServ.setLogin( userName );
			inetServ.setPassword( &quot;Redback&quot; );
		}
	}
}
&lt;/source&gt;

== Авторизация по 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 подставляет
&lt;source lang='java'&gt;
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( &quot;(.+) vlan-id (\\d+)&quot; );

	@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( &quot;vlanId=&quot; + vlanId );

				request.setOption( InetRadiusProcessor.VLAN_ID, vlanId );
			}
			else
			{
				logger.error( &quot;vlanId not found!&quot; );
			}
		}
		else
		{
			logger.error( &quot;Attribute Nas-Port-Id not found!&quot; );
		}
	}
}

&lt;/source&gt;


== Ссылки ==
#[http://forum.bgbilling.ru/viewtopic.php?f=44&amp;t=5361&amp;start=15  Обсуждение на форуме]</rev>
        </revisions>
      </page>
      <page pageid="456" ns="0" title="SMS рассылка через SMPP">
        <revisions>
          <rev xml:space="preserve">Приведу пример рассылки SMS через SMPP.

Создаем таблицу в базе:
&lt;source lang=&quot;sql&quot;&gt; 
CREATE TABLE `sms_informer` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `phone_number` varchar(12) NOT NULL,
  `sent` tinyint(1) NOT NULL DEFAULT '0',
  `last_operation` datetime DEFAULT NULL,
  `status` text,
  `message_id` text,
  `cmd` text,
  `data` text,
  `seq` text,
  `delivered` tinyint(1) NOT NULL DEFAULT '0',
  `message_text` text NOT NULL,
  `processed` tinyint(1) NOT NULL DEFAULT '0',
  `cid` int(10) unsigned NOT NULL,
  `source_addr` varchar(7) NOT NULL,
  `create_dt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`),
  KEY `cid` (`cid`) USING BTREE
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=cp1251;
&lt;/source&gt;

Добавляем в cron задание на запуск скрипта:
&lt;source lang=&quot;perl&quot;&gt;
#!/usr/local/bin/perl -w

use strict;
use DBI;
use Data::Dumper;
use Time::Local;
use Encode;
use Net::SMTP;
use Fcntl &quot;:flock&quot;;
use Net::SMPP;
use MIME::Base64;
use cyrillic qw/866 uni2win win2koi koi2win win2uni convert locase upcase detect/;

my $lockfilename=&quot;/var/run/sms.lock&quot;;

my $host = '';
my $port = '9932';
my $system_id = '';
my $password = '';

if (!open(LOCKFILE, &quot;&gt;$lockfilename&quot;)) {
  Debug(&quot;Can't lock file!\n&quot;); 
  exit(1);
};
if (!flock(LOCKFILE, LOCK_EX | LOCK_NB)) {
  #процесс уже запущен
  Debug(&quot;Process already running!\n&quot;) ;
  exit(1);
};

my $MySQLdb = DBI-&gt;connect('dbi:mysql:database=bgbilling;host=localhost;port=3306','sms','password');
if ($MySQLdb){
  $MySQLdb-&gt;do('SET CHARACTER SET cp1251');
  $MySQLdb-&gt;do('SET NAMES cp1251');
};

my $smpp = Net::SMPP-&gt;new_transmitter(
                $host,
                port =&gt; $port,
                system_id =&gt; $system_id,
                password  =&gt; $password,
                system_type =&gt; 'SMPP', #?
        );
											
if (!$smpp) {
    Debug(&quot;ERROR (1) Couldn't connect to SMSC\n&quot;);
    exit(4);
}

												
my $res = MySQLSelect(
  &quot;Get sms tasks&quot;,
  &quot;&gt;=0&quot;,
  &quot;select id, phone_number, message_text, source_addr from sms_informer where processed = false order by phone_number, id and create_dt&gt;=ADDDATE(CURDATE(), -1)&quot;);
while (my ($id, $dest, $message_text, $source_addr) = $res-&gt;fetchrow){
#  print &quot;$id, $dest, $message_text, $source_addr\n&quot;;
  send_sms($id, $dest, $message_text, $source_addr);
};
                                    

#sleep 300;

close(LOCKFILE);
unlink($lockfilename);
      
    
#exit;
########################################################################
sub send_sms {
        my $id = shift;
        my $dest = shift;
        my $text = shift;
        my $source_addr = shift;
			
        my @parts = split_msg($id, $text);
        my %part;
        my $resp_pdu;
						
        Debug(get_timestamp().&quot; INFO sending to $dest ($id) SMS '$text'\n&quot;);
							
        if ($text =~ m/[^a-zA-Z0-9 \,\.\?\!\;\:\'\&quot;\[\]\{\}\(\)\-\+\=\*\&amp;\^\%\$\#\@\~\`\\\/\&lt;\&gt;\|]/) {
            $text = convert(&quot;win&quot;, &quot;uni&quot;, $text);
        }

        if (scalar(@parts) eq 1) {
	
            %part = %{$parts[0]};
		
            $resp_pdu = $smpp-&gt;submit_sm(
                    destination_addr =&gt; $dest,
                    data_coding =&gt; $part{encoding},
                    short_message =&gt; $part{msg},
                    service_type =&gt; '',
                    esm_class =&gt; 0x00,
                    source_addr_ton =&gt; 0x00,
                    source_addr_npi =&gt; 0x00,
                    source_addr =&gt; $source_addr #,
                    #registered_delivery =&gt; 0x01
            );
																																		
            if (!$resp_pdu) {
                    Debug(get_timestamp().&quot; ERROR (400) Couldn't submit short message.\n&quot;);
                    return 0;
            }
																																												
            Debug(get_timestamp().&quot; DEBUG dump PDU:\n&quot;);
            for my $key (keys %{$resp_pdu}) {
		if (!exists($resp_pdu-&gt;{$key}) || !defined($resp_pdu-&gt;{$key})){next;}; 
		Debug(&quot;$key =&gt; &quot;.$resp_pdu-&gt;{$key}.&quot;\n&quot;); 
	    }
            Debug(&quot;-----------------------\n&quot;);

        } else {

            for my $item (@parts) {
                %part = %{$item};
                Debug(get_timestamp().&quot; INFO sending part &quot;.$part{seq}.&quot; of SMS to $dest\n&quot;);
						
                $resp_pdu = $smpp-&gt;submit_sm(
                    destination_addr =&gt; $dest,
                    data_coding =&gt; $part{encoding},
                    short_message =&gt; $part{msg},
                    service_type =&gt; '',
                    source_addr_ton =&gt; 0x00,
                    source_addr_npi =&gt; 0x00,
                    source_addr =&gt; $source_addr,
                    registered_delivery =&gt; 0x01,

                    sar_total_segments =&gt; scalar(@parts),
                    sar_segment_seqnum =&gt; $part{seq},
                    sar_msg_ref_num =&gt; $part{id}
                );
																																																										
                if (!$resp_pdu) {
                    Debug(get_timestamp().&quot; ERROR (401) Couldn't submit short message.\n&quot;);
                    return 0;
                }

                Debug(get_timestamp().&quot; DEBUG dump PDU:\n&quot;);
                for my $key (keys %{$resp_pdu}) {
    		    if (!exists($resp_pdu-&gt;{$key}) || !defined($resp_pdu-&gt;{$key})){next;}; 
		    Debug(&quot;$key =&gt; &quot;.$resp_pdu-&gt;{$key}.&quot;\n&quot;);
		}; 
                Debug(&quot;-----------------------\n&quot;);
            }
    }

    my $message_id = (exists($resp_pdu-&gt;{message_id}) ? &quot;'&quot;.$resp_pdu-&gt;{message_id}.&quot;'&quot; : &quot;NULL&quot; );
    my $seq = (exists($resp_pdu-&gt;{seq}) ? &quot;'&quot;.$resp_pdu-&gt;{seq}.&quot;'&quot; : &quot;NULL&quot; );
    my $status = (exists($resp_pdu-&gt;{status}) ? &quot;'&quot;.$resp_pdu-&gt;{status}.&quot;'&quot; : &quot;NULL&quot; );
    my $cmd = (exists($resp_pdu-&gt;{cmd}) ? &quot;'&quot;.$resp_pdu-&gt;{cmd}.&quot;'&quot; : &quot;NULL&quot; );
    my $data = (exists($resp_pdu-&gt;{data}) ? $resp_pdu-&gt;{data} : &quot;NULL&quot; );
    my $sent = (($status eq &quot;'0'&quot;) ? 'true' : 'false');
    my $delivered = ( $sent and exists($resp_pdu-&gt;{cmd}) and ( $resp_pdu-&gt;{cmd} eq 0x80000004 ) ? 'true' : 'false');
							
    if ($data ne &quot;NULL&quot;) {
        $data = encode_base64($data);
        $data =~ s/\s+//mg;
        $data = &quot;'$data'&quot;;
    }
#UPDATE
    my $res2 = MySQLExec(
       &quot;UPDATE task&quot;,
       &quot;UPDATE sms_informer SET last_operation=NOW(), processed=true, sent=$sent, delivered=$delivered, message_id=$message_id, seq=$seq, status=$status, cmd=$cmd, data=$data WHERE id=$id&quot;
    );
				      
};																########################################################################
sub split_msg {
    my $id = shift;
    my $text = shift;
    my $encoding = 0x00;
    if ($text =~ m/[^a-zA-Z0-9 \,\.\?\!\;\:\'\&quot;\[\]\{\}\(\)\-\+\=\*\&amp;\^\%\$\#\@\~\`\\\/\&lt;\&gt;\|]/) {
        $encoding = 0x08;
    }
    my @parts = ();
    push @parts, { id =&gt; $id, seq =&gt; 1, encoding =&gt; $encoding, msg =&gt; ($encoding eq 0x08 ?  convert(&quot;win&quot;, &quot;uni&quot;, $text) : $text) };
    return @parts;
};										
########################################################################
sub get_timestamp
{
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdist) = localtime time;
    return sprintf (&quot;%04d-%02d-%02d %02d:%02d:%02d&quot;, $year+1900,$mon+1,$mday,$hour,$min,$sec);
};		
########################################################################
sub Debug {
  my $str = $_[0];
  $str =~ s/\n//g;
#  printf &quot;%-10s\n&quot;,  $str;
};
#######################################################################
sub MySQLSelect{
  my ($header, $condition, $SQL_QUERY) = @_;
  if (!$header) {return -1};
  if (!$condition) {return -1};
  if (!$SQL_QUERY) {return -1};
  my $res = $MySQLdb-&gt;prepare($SQL_QUERY);
  if (!$res-&gt;execute){
    Debug(&quot;Error. Can't $SQL_QUERY&quot;);
    Debug(DBI::errstr);
  }else{
    my $ntuples = $res-&gt;rows;
    my $op = '$ntuples'.&quot;$condition&quot;;
    my $condition_stat = eval $op;
    if (!$condition_stat){
      Debug(&quot;Error result (ntuples: $ntuples, condition: $condition) for $SQL_QUERY&quot;);
    };
  };
  return $res;
}
########################################################################
sub MySQLExec{
  my ($header, $SQL_QUERY) = @_;
  if (!$header) {return -1};
  if (!$SQL_QUERY) {return -1};
  if (!$MySQLdb-&gt;do($SQL_QUERY)){
    Debug(&quot;Error. Can't $SQL_QUERY&quot;);
    Debug(DBI::errstr);
  };
};
########################################################################		                                               
&lt;/source&gt;
У нас этот скрипт запускается раз в 10 минут.

Теперь для отправки SMS сообщение необходимо добавить запись в таблицу sms_informer через BGBS или сторонней программы.
Например так:
&lt;source lang=&quot;sql&quot;&gt; 
INSERT INTO sms_informer (cid,phone_number,message_text,source_addr,create_dt) VALUES ('111111','79106667788','Privet abonent','0000111',NOW())
&lt;/source&gt;</rev>
        </revisions>
      </page>
    </pages>
  </query>
</api>