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