Настройка разбора опции DHCP Option 82

Перед прочтением данной главы рекомендуется к прочтению глава Процессор InetDhcpProcessor.

Для корректной работы нужно правильно извлекать значения agentRemoteId, circuitId (port/VLAN) из DHCP-пакета. А в случае использования IPoE c Cisco ISG или SmartEdge еще и из RADIUS-пакетов (в этом случае значения субопций option 82 находятся внутри RADIUS-пакетов). Извлекать значения можно с помощью конфигурации с указанием regex, с помощью конфигурации с указанием позиций значений, а также с помощью скрипта предобработки пакета.

Извлечение значений идентифицирующих абонента из DHCP-пакета

Для извлечения значения требуется указать код субопции, а также один или два regex - hex и string. hex указывает, что нужно выбрать в опции в 16-ричном формате (один байт - это два символа), string - указывает, что нужно выбрать в опции, преобразованной в строку. Предположим, у нас в DHCP-пакете присутствует такая option 82:

Agent information{82}=
sub{1}={076D00020201}
sub{2}={D067B3932607}

agentRemoteId является вся HEX-строка D067B3932607 в субопции 2 - это MAC-адрес, укажем как его извлечь:

dhcp.option82.agentRemoteId.code=2
dhcp.option82.agentRemoteId.hex=.*

Чтобы получить номер порта, который находится в субопции 1 в третьем байте ({076D00020201}), указываем:

dhcp.option82.interfaceId.code=1
dhcp.option82.interfaceId.hex=^\w{6}(\w{2})

Таким образом с помощью regex мы указываем, что нам нужны два символа (02) в этой HEX-строке.

Для того чтобы получить VLAN, который находится в субопции 1 в первых двух байтах ({076D00020201}), нужно указать:

dhcp.option82.vlanId.code=1
dhcp.option82.vlanId.hex=^(\w{2})

Подобным образом можно извлечь и SVLAN:

dhcp.option82.agentSvlanId.code=1
dhcp.option82.agentSvlanId.hex=^(\w{2})

Или логин для поиска по логину (для схемы PON, когда в agentRemoteId приходит MAC-адрес ONU):

dhcp.option82.login.code=2
dhcp.option82.login.hex=.*

Однако приходящие значения могут представлять собой строку:

Agent information{82}=
sub{1}={7072617664792D3331612D706F7274332D3330352D766C616E}
sub{2}={33362D6C65736F7A61766F642065746820312F313235}

Такие значения обычно длиннее, чем бинарный вариант. В данном примере в опции 1 записана строка "pravdy-31a-port3-305-vlan", в опции 2 - "36-lesozavod eth 1/125". Для извлечения значения из такой опции можно использовать regex, прописанный в параметре string:

dhcp.option82.interfaceId.code=1
dhcp.option82.interfaceId.string=port(\d+)
dhcp.option82.vlanId.code=1
dhcp.option82.vlanId.string=(\d+)-vlan

Если hex при этом не указан, то это равнозначно, как если бы он был указан равным ".*":

dhcp.option82.interfaceId.code=1
dhcp.option82.interfaceId.hex=.*
dhcp.option82.interfaceId.string=port(\d+)

Если в 16-ричном виде присутствует префикс, который нужно исключить перед преобразованием в строку, то можно использовать оба regex - сначала с помощью hex выберем 16-ричное значение, затем с помощью string преобразуем его в строку и выберем подстроку. Например, у нас в значении есть префикс-длина:

Agent information{82}=
sub{1}={00197072617664792D3331612D706F7274332D3330352D766C616E}
sub{2}={001633362D6C65736F7A61766F642065746820312F313235}

Тогда исключить первые два байта и получить порт можем так:

dhcp.option82.interfaceId.code=1
dhcp.option82.interfaceId.hex=^.{4}(.+)$
dhcp.option82.interfaceId.string=port(\d+)

Regex-ов можно указать несколько - это может быть удобно при наличии устройств разных марок - они могут посылать option 82 в разном формате:

dhcp.option82.agentRemoteId.code=2
dhcp.option82.agentRemoteId.1.hex=^0006(.{6})$
dhcp.option82.agentRemoteId.2.hex=.*

Для таких случаев рекомендуется, чтобы в дереве устройств биллинга было устройство с таким идентификатором (agentRemoteId), у него был свой тип устройства, а в конфигурации типа устройства была задана конфигурация для извлечения порт/VLAN и т.п. (см. главу Разные типы устройств ). Но если разницу можно выделить через regex, то извлечение полей circuitId можно также произвести набором regex:

dhcp.option82.interfaceId.code=1
dhcp.option82.interfaceId.1.hex=^0006\w{2}(\w{2})
dhcp.option82.interfaceId.2.hex=.*
dhcp.option82.interfaceId.2.string=port(\d+)

При осуществлении настройки проверить ваш Regex вы можете на одном из множества онлайн-сервисов, например, https://regex101.com/. Для преобразования набора байт в 16-ричном представлении в строку также можно воспользоваться онлайн-сервисом.

Извлечение значений идентифицирующих абонента из RADIUS-пакета

Из RADIUS-пакета необходимо извлечь Agent-Remote-Id и Circuit-Id - значения DHCP option 82. Значения порта/VLAN/SVLAN будут извлечены из Circuit-Id с помощью правил описанных в параметрах dhcp.option82.* (см. выше).

Если приходящие значения закодированы в бинарном формате (например, значение Agent-Remote-Id совпадает с MAC-адресом агентского устройства) и нет префикса с указанием длины опции, то достаточно указать RADIUS-vendor и тип атрибута, например, для Redback Agent-Remote-Id и Agent-Circuit-Id:

radius.agentRemoteId.vendor=2352
radius.agentRemoteId.type=96
radius.option82.circuitId.vendor=2352
radius.option82.circuitId.type=97

Если в значениях опций присутствует префикс-длина размером два байта, то его нужно исключить при получении agentRemoteId:

radius.agentRemoteId.vendor=2352
radius.agentRemoteId.type=96
radius.agentRemoteId.hex=^.{4}(.*)$
radius.option82.circuitId.vendor=2352
radius.option82.circuitId.type=97

Если в значении закодирована строка, а не бинарные значения, например:

Agent-Remote-Id={70 72 61 76 64 79 2D 33 36 61 2D 70 6F 72 74 35 2D 38 30 35 2D 76 6C 61 6E}
Agent-Circuit-Id={34 36 2D 6C 65 73 6F 7A 61 76 6F 64 20 65 74 68 20 30 2F 31 32 35}

то в конфигурации указываем преобразование в строку:

radius.agentRemoteId.vendor=2352
radius.agentRemoteId.type=96
radius.agentRemoteId.string=.*
radius.option82.circuitId.vendor=2352
radius.option82.circuitId.type=97

Circuit-Id в двух последних случаях мы извлекаем как есть - подразумевается, что извлеченное значение будет идентично значению DHCP-опции в DHCP-пакете и его обработка уже прописана в параметрах dhcp.option82.*.

Для более сложных вариантов возможно извлечение с помощью предобработки (см. далее).

Извлечение значений, идентифицирующих абонента из DHCP-пакета, c указанием позиции и длины

Это старый вариант извлечения значений, но при больших нагрузках (от 300 тыс. сессий онлайн) он может быть предпочтительнее, т.к. будет тратить меньше ресурсов процессора.

# Если в субопции отсутствует заголовок с длиной субопции, то укажите 0. Иначе укажите длину заголовка.
# Данный параметр используется в том числе, для того, чтобы извлеченные значения circuitId из DHCP-пакета и из RADIUS-пакета были идентичны.
# Соответственно, значение position нужно указывать относительно removeHeader.
dhcp.option82.removeHeader=0
# agentRemoteId обычно находится в субопции 2
dhcp.option82.agentRemoteId.code=2
# позиция значения внутри субопции
dhcp.option82.agentRemoteId.position=2
# если длина значения может изменятся и нужно брать значение до конца субопции, то укажите -1
dhcp.option82.agentRemoteId.length=6
# 0, если remoteId в бинарном виде, например, MAC-адрес; 1, если там закодирована строка
dhcp.option82.agentRemoteId.type=0
# interfaceId обычно находится в субопции 1 (circuitId)
dhcp.option82.interfaceId.code=1
# позиция значения внутри субопции
dhcp.option82.interfaceId.position=5
# длина значения
dhcp.option82.interfaceId.length=1
# vlanId обычно находится в субопции 1 (circuitId)
dhcp.option82.vlanId.code=1
# позиция значения внутри субопции
dhcp.option82.vlanId.position=2
# длина значения
dhcp.option82.vlanId.length=2

Разные типы устройств

Если используются разные типы устройств, у которых разные форматы circuitId, тип поиска DHCP-устройства должен быть 0 (в этом режиме сначала находится устройство по giaddr, у него вызывается preprocess, затем находится агентское ус-во, у него тоже вызывается preprocess) или 1 (в этом режиме сначала находится устройство по giaddr, затем находится агентское ус-во, у него тоже вызывается preprocess).

dhcp.deviceSearchMode=0

Конфигурация парсинга agentRemoteId должна быть указана в устройстве, с которого приходит запрос на InetAccess (т.е. чей giaddr указан в пакете). А конфигурация извлечения порта/VLAN из curcuitId должна быть указана в агентских типах устройствах. Таким образом InetAccess найдет relay agent по giaddr, по его конфигурации извлечет agentRemoteId, по agentRemoteId найдет дочернее агентское устройство и уже по его конфигурации извлечет значения порта/VLAN.

Разные типы устройств c разным форматом agentRemoteId

В этом случае нельзя однозначно указать в конфигурации как извлечь agentRemoteId, в отличие от варианта выше.

Поэтому нужно воспользоваться набором regex, описанных выше:

dhcp.option82.agentRemoteId.code=2
dhcp.option82.agentRemoteId.1.hex=^0006(.{6})$
dhcp.option82.agentRemoteId.2.hex=.{8}(.{6})

Или же воспользоваться предобработкой пакетов. Для этого укажите тип поиска DHCP-устройства = 0 (в этом режиме сначала находится устройство по giaddr, у него вызывается preprocess, затем находится агентское ус-во, у него тоже вызывается preprocess).

dhcp.deviceSearchMode=0

Расширьте обработчик процессора протокола типа устройства, с с которого приходит запрос на InetAccess (т.е. чей giaddr указан в пакете). По giaddr InetAccess однозначно найдет устройство. Затем вызовет у него предобработку, в которой нужно определить как распарсить и в ручную проставить agentRemoteId.

import java.util.Arrays;
import ru.bitel.bgbilling.kernel.network.dhcp.DhcpPacket;
import ru.bitel.bgbilling.kernel.network.dhcp.DhcpProtocolHandler;
import ru.bitel.bgbilling.modules.inet.access.sa.ProtocolHandlerAdapter;
import ru.bitel.bgbilling.modules.inet.dhcp.InetDhcpProcessor;
public class Dhcp82ProtocolHandler
extends ProtocolHandlerAdapter
implements DhcpProtocolHandler
{
@Override
public void preprocessDhcpRequest( DhcpPacket request, DhcpPacket response )
throws Exception
{
byte[] agentRemoteId = request.getSubOption( (byte)2 ).value;
// DLink
if( agentRemoteId.length == 8 )
{
request.setOption( InetDhcpProcessor.AGENT_REMOTE_ID, Arrays.copyOfRange( agentRemoteId, 2, 6 ) );
}
else
{
request.setOption( InetDhcpProcessor.AGENT_REMOTE_ID, Arrays.copyOfRange( agentRemoteId, 5, 6 ) );
}
}
}

Конфигурация извлечения порта/VLAN из curcuitId должна быть указана в агентских типах устройствах. Таким образом InetAccess найдет relay agent по giaddr, предобработка извлечет и проставит agentRemoteId, по agentRemoteId InetAccess найдет дочернее агентское устройство и уже по его конфигурации извлечет значения порта/VLAN.

Cisco ISG и SmartEdge

В отличие от схемы DHCP82 без Cisco ISG/SmartEdge, здесь еще нужно извлечь remoteId и circuitId из RADIUS-пакета.

# Если в значении атрибута отсутствует заголовок с длиной субопции, то укажите 0. Иначе укажите длину заголовка.
# Данный параметр используется в том числе, для того, чтобы извлеченные значения circuitId из DHCP-пакета и из RADIUS-пакета были идентичны.
# Соответственно, значение position нужно указывать относительно removeHeader.
radius.agent.option.removeHeader=2
# SmartEdge
# код атрибута
radius.agent.option.remoteId.type=96
# позиция в значении атрибута
radius.agent.option.remoteId.position=0
# длина
radius.agent.option.remoteId.length=-1
radius.agent.option.circuitId.type=97
 
# или
radius.agent.option.remoteId.type=202
radius.agent.option.remoteId.position=0
radius.agent.option.remoteId.length=-1
radius.agent.option.circuitId.type=202
# Cisco ISG
radius.agent.option.remoteId.type=1
radius.agent.option.remoteId.prefix=remote-id-tag=
radius.agent.option.circuitId.type=1
radius.agent.option.circuitId.prefix=circuit-id-tag=

Cisco ISG и SmartEdge и разные типы устройств c разным форматом agentRemoteId

В этом случае нельзя однозначно указать в конфигурации как извлечь agentRemoteId и curcuitId из RADIUS-пакета. Поэтому это нужно сделать в предобработке (извлечение из RADIUS-пакета и сейчас происходит в предобработке, но согласно конфигурации - это можно увидеть в динамических классах ISGProtocolHandler и SmartEdgeClipsProtocolHandler). Расширьте класс предобработки Cisco ISG/SmartEdge:

import java.util.Arrays;
import ru.bitel.bgbilling.kernel.network.radius.RadiusAttribute;
import ru.bitel.bgbilling.kernel.network.radius.RadiusPacket;
import ru.bitel.bgbilling.modules.inet.radius.InetRadiusProcessor;
public class XSmartEdgeClipsProtocolHandler
extends SmartEdgeClipsProtocolHandler
{
@Override
protected void setAgentOptions( RadiusPacket request )
{
RadiusAttribute<byte[]> agentRemoteIdAttribute = request.getAttribute( 2352, 96 );
RadiusAttribute<byte[]> circuitRemoteIdAttribute = request.getAttribute( 2352, 97 );
byte[] agentRemoteId = agentRemoteIdAttribute.getValue();
byte[] circuitRemoteId = circuitRemoteIdAttribute.getValue();
// DLink
if( agentRemoteId.length == 8 )
{
request.setOption( InetRadiusProcessor.AGENT_REMOTE_ID, Arrays.copyOfRange( agentRemoteId, 2, 6 ) );
request.setOption( InetRadiusProcessor.AGENT_CIRCUIT_ID, Arrays.copyOfRange( circuitRemoteId, 2, circuitRemoteId.length - 2 ) );
}
else
{
request.setOption( InetRadiusProcessor.AGENT_REMOTE_ID, Arrays.copyOfRange( agentRemoteId, 5, 6 ) );
request.setOption( InetRadiusProcessor.AGENT_CIRCUIT_ID, Arrays.copyOfRange( circuitRemoteId, 2, circuitRemoteId.length - 2 ) );
}
}
}

Если в DHCP-пакете указан giaddr Cisco/SmartEdge, то в этот класс нужно добавить предобработку DHCP

import java.util.Arrays;
import ru.bitel.bgbilling.kernel.network.dhcp.DhcpPacket;
import ru.bitel.bgbilling.kernel.network.radius.RadiusAttribute;
import ru.bitel.bgbilling.kernel.network.radius.RadiusPacket;
import ru.bitel.bgbilling.modules.inet.dhcp.InetDhcpProcessor;
import ru.bitel.bgbilling.modules.inet.radius.InetRadiusProcessor;
public class XSmartEdgeClipsProtocolHandler
extends SmartEdgeClipsProtocolHandler
{
@Override
protected void setAgentOptions( RadiusPacket request )
{
RadiusAttribute<byte[]> agentRemoteIdAttribute = request.getAttribute( 2352, 96 );
RadiusAttribute<byte[]> circuitRemoteIdAttribute = request.getAttribute( 2352, 97 );
byte[] agentRemoteId = agentRemoteIdAttribute.getValue();
byte[] circuitRemoteId = circuitRemoteIdAttribute.getValue();
// DLink
if( agentRemoteId.length == 8 )
{
request.setOption( InetRadiusProcessor.AGENT_REMOTE_ID, Arrays.copyOfRange( agentRemoteId, 2, 6 ) );
request.setOption( InetRadiusProcessor.AGENT_CIRCUIT_ID, Arrays.copyOfRange( circuitRemoteId, 2, circuitRemoteId.length - 2 ) );
}
else
{
request.setOption( InetRadiusProcessor.AGENT_REMOTE_ID, Arrays.copyOfRange( agentRemoteId, 5, 6 ) );
request.setOption( InetRadiusProcessor.AGENT_CIRCUIT_ID, Arrays.copyOfRange( circuitRemoteId, 2, circuitRemoteId.length - 2 ) );
}
}
@Override
public void preprocessDhcpRequest( DhcpPacket request, DhcpPacket response )
throws Exception
{
byte[] agentRemoteId = request.getSubOption( (byte)2 ).value;
// DLink
if( agentRemoteId.length == 8 )
{
request.setOption( InetDhcpProcessor.AGENT_REMOTE_ID, Arrays.copyOfRange( agentRemoteId, 2, 6 ) );
}
else
{
request.setOption( InetDhcpProcessor.AGENT_REMOTE_ID, Arrays.copyOfRange( agentRemoteId, 5, 6 ) );
}
}
}

Если же giaddr в DHCP-пакете relay agent'а, от которого получил запрос Cisco/SmartEdge, то для типа устройства relay agent'а нужно указать отдельный обработчик, см. выше .