Настройка разбора опции DHCP Option 82
Перед прочтением данной главы рекомендуется к прочтению глава Процессор InetDhcpProcessor.
Извлечение значений идентифицирующих абонента из DHCP-пакета
Извлечение значений идентифицирующих абонента из RADIUS-пакета
Извлечение значений, идентифицирующих абонента из DHCP-пакета, c указанием позиции и длины
Cisco ISG и SmartEdge и разные типы устройств c разным форматом agentRemoteId
Для корректной работы нужно правильно извлекать значения 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'а нужно указать отдельный обработчик, см. выше .