Процессор InetDhcpProcessor
Предназначен для выдачи IP-адресов по протоколу DHCP с опцией 82. Опцию в запрос должен подставить коммутатор, пропускающий запрос, далее запрос обязательно должен переслать DHCP-Relay. Идентификация сервисов осуществляется на основании полей circuitId и remoteId, но также могут быть настроены любые другие поля, определяющие порт коммутатора клиента.
Загружает список устройств-коммутаторов, начиная с корневого узла. Типы устройств-коммутаторов определяются в переменной конфигурации корневого узла dhcp.relay.deviceTypeIds через запятую. Также загружаются привязанные к коммутаторам сервисы договоров.
Когда приходит DHCP-запрос, из него извлекается поле giaddr (Relay-IP). Осуществляется поиск устройства-релея сначала по совпадению этого поля с адресом устройства в биллинге. Затем, если поиск был отрицательным - осуществляется поиск по совпадению IP-адреса, с которого пришёл DHCP-запрос с IP-адресом устройства.
Для обработки DHCP-RENEW-запросов без опции DHCP Option 82 в конфигурации корневого устройства необходимо указать:
# Нужно ли обрабатывать RENEW-запросы без Option 82 (требуется перезапуск InetAccess)
# 0 - нет (по умолчанию), 1 - да (рекомендуется)
dhcp.renew=
1
Если по каким-то причинам клиентские устройства (например, NetGear JWNR2000) в DHCP-REQUEST посылают xid, отличный от DHCP-DISCOVER, можно убрать привязку к xid-запросам, прописав в конфигурации корневого устройства:
# Привязка к xid DHCP-запросов (требуется перезапуск InetAccess)
# 0 - выкл., 1 (по умолчанию) - вкл.
dhcp.xid=
0
В случае, если необходимо отвечать на INFO-запросы, то в конфигурации нужно указать:
# Нужно ли отвечать на DHCP-INFO-запросы
# 0 - выкл. (по умолчанию), 1 - вкл.
dhcp.offer.infoDiscover=
1
Далее алгоритм работы определяется переменными конфигурации найденного устройства-релея. Следующие параметры определяют, какие опции извлекаются для идентификации устройства-коммутатора клиента и непосредственно клиента по порту или VLAN.
# Удаление заголовка, при необходимости, 0 - не удалять, 2 - 2 удалить байта (тип+длина) из значения DHCP-опции.
# При удалении поля position для agentRemoteId, vlanId, interfaceId нужно уменьшить на тоже кол-во байт
#dhcp.option82.removeHeader=0
dhcp.option82.removeHeader=
2
# Параметры для извлечения из пакета agentRemoteId
# вид значения в опции agentRemoteId: 0 (по умолчанию) - байты, 1 - строка
#dhcp.option82.agentRemoteId.type=0
# код субопции 82, содержащей идентификатор коммутатора клиента, позиция и длина последовательности идентификатора
dhcp.option82.agentRemoteId.code=
2
dhcp.option82.agentRemoteId.position=
0
dhcp.option82.agentRemoteId.length=
6
# код субопции 82, содержащей VLAN, позиция и длина в субопции
dhcp.option82.vlanId.code=
1
dhcp.option82.vlanId.position=
0
dhcp.option82.vlanId.length=
2
# код субопции 82, содержащей интерфейс, позиция и длина в субопции
dhcp.option82.interfaceId.code=
1
dhcp.option82.interfaceId.position=
3
dhcp.option82.interfaceId.length=
1
Параметры, описанные выше подойдут для обработки такого пакета:
Message type: BOOT_REQUEST
Dhcp message type: DHCP Discover{
1
}
htype:
1
, hlen:
6
, hops:
1
xid:
1067065417
, secs:
0
, flags:
0
Client IP:
0.0
.
0.0
Your IP:
0.0
.
0.0
Server IP:
0.0
.
0.0
Relay IP:
109.233
.
170.1
Client MAC: {00804840A46F}
Host name{
12
}={support-desktop}
Parameter request list{
55
}={
1
,
28
,
2
,
3
,
15
,
6
,
119
,
12
,
44
,
47
,
26
,
121
,
42
}
Agent information{
82
}=
sub{
1
}={
000403420101
}
sub{
2
}={00060012CF539F5E}
В данном случае будет проигнорирован заголовок: sub{1}={03420101}, sub{2}={0012CF539F5E} и извлечены значения agentRemoteId=0012CF539F5E (MAC-адрес в виде набора байтов), VLAN=0x0342, interfaceId=0x01.
От некоторых коммутаторов значения могут приходить в строковом виде, например: sub{2}={3137322E31362E39392E33363A313031} (на самом деле, это обычная строка в шестнадцетиричном представлении), для обработки этой опции нужен такой конфиг:
# Удаление заголовка, при необходимости, 0 - не удалять, 2 - 2 удалить байта (тип+длина) из значения DHCP-опции.
# При удалении поля position для agentRemoteId, vlanId, interfaceId нужно уменьшить на тоже кол-во байт
dhcp.option82.removeHeader=
0
# Параметры для извлечения из пакета agentRemoteId
# вид значения в опции agentRemoteId: 0 (по умолчанию) - байты, 1 - строка
#dhcp.option82.agentRemoteId.type=1
# код субопции 82, содержащей идентификатор коммутатора клиента, позиция и длина последовательности идентификатора
dhcp.option82.agentRemoteId.code=
2
dhcp.option82.agentRemoteId.position=
0
dhcp.option82.agentRemoteId.length=
0
# Извлечение vlanId и interfaceId из строкового представления не поддерживается, данную процедуру нужно будет провести в
# обработчике процессора протокола, как описано ниже.
# код субопции 82, содержащей VLAN, позиция и длина в субопции
#dhcp.option82.vlanId.code=1
#dhcp.option82.vlanId.position=0
#dhcp.option82.vlanId.length=2
# код субопции 82, содержащей интерфейс, позиция и длина в субопции
#dhcp.option82.interfaceId.code=1
#dhcp.option82.interfaceId.position=3
#dhcp.option82.interfaceId.length=1
При необходимости, данные параметры можно установить вручную в обработчике процессора протокола, в методе preprocessDhcpRequest (аналогично InetRadiusProcessor).
final
DhcpOption agentRemoteId = request.getSubOption( option82RemoteIdCode );
if
( agentRemoteId !=
null
)
{
byte
[] value =
new
byte
[option82RemoteIdLength];
System.arraycopy( agentRemoteId.value, option82RemoteIdPosition, value,
0
, option82RemoteIdLength );
request.setOption( InetDhcpProcessor.AGENT_REMOTE_ID, value );
}
final
DhcpOption vlanId = request.getSubOption( option82VlanIdCode );
if
( vlanId !=
null
)
{
int
vlan = InetUtils.parseInt( vlanId.value, option82VlanIdPosition, option82VlanIdLength );
request.setOption( InetDhcpProcessor.VLAN_ID, vlan );
}
Пример обработки для строкового представления (извлечение agentRemoteId не обязательно, если указано dhcp.option82.agentRemoteId.type=1):
@Override
public
void
preprocessDhcpRequest( DhcpPacket request, DhcpPacket response )
throws
Exception
{
DhcpOption circuitId = request.getSubOption( (
byte
)
1
);
DhcpOption remoteId = request.getSubOption( (
byte
)
2
);
request.setOption( InetDhcpProcessor.AGENT_REMOTE_ID,
new
String( remoteId.value,
"UTF-8"
) );
request.setOption( InetDhcpProcessor.INTERFACE_ID,
new
String( circuitId.value,
"UTF-8"
) );
}
По описанным выше значениям AGENT_REMOTE_ID, INTERFACE_ID и VLAN, которые будут извлечены из пакета, происходит поиск устройства и сервиса. Конфигурация поиска устройства и сервиса на устройстве:
dhcp.deviceSearchMode=<deviceSearchMode>
dhcp.servSearchMode=<servSearchMode>
#dhcp.servSearchMode=<servSearchMode>-<subServSearchMode>
Идентификация коммутатора, расположенного под релеем и сервиса на коммутаторе, может производится в нескольких режимах. Режимы определяется параметром конфигурации устройства-релея.
<deviceSearchMode> может принимать значения:
0 (рекомендуется) - по giaddr или IP-адресу источника идет поиск устройства, далее у этого устройства вызывается предобработка preprocessDhcpRequest (где можно при необходимости извлечь и установить AGENT_REMOTE_ID, а также INTERFACE_ID или VLAN_ID), далее по установленному AGENT_REMOTE_ID или, если AGENT_REMOTE_ID не установлен - по конфигурации dhcp.option82.agentRemoteId.x agentRemoteId извлекается из пакета и идет поиск агентского устройства по совпадению идентификатора устройства, далее у агентского устройства, если таковое найдено вызывается preprocessDhcpRequest (где можно при необходимости извлечь и установить INTERFACE_ID или VLAN_ID). Здесь запоминаются оба устройства, как deviceId и agentDeviceId;
1 - по giaddr или IP-адресу источника идет поиск устройства, по его конфигурации идет извлечение agentRemoteId, далее по agentRemoteId идет поиск агентского устройства. Здесь запоминается последнее найденное устройство как deviceId, agentDeviceId для биллинга будет 0;
2 - по giaddr или IP-адресу источника идет поиск устройства, найденное устройство будет запомнено как deviceId, agentDeviceId для биллинга будет 0.
Поиск сервиса происходит на агентском устройстве (agentDeviceId), если оно найдено, иначе на устройстве (deviceId).
<servSearchMode> может принимать значения:
1 - поиск по интерфейсу на (найденном) устройстве;
2 - поиск по VLAN'у на устройстве;
3 - поиск на устройстве по интерфейсу и MAC-адресу;
4 - поиск по VLAN'у на устройстве и его дочерних устройствах;
5 - поиск по MAC-адресу на устройстве;
6 - поиск по MAC-адресу на устройстве и дочерних устройствах;
10 - поиск по MAC-адресу на всех устройствах;
11 - поиск по VLAN и MAC-адресу на устройстве, а также на его потомках и его предках;
12 - поиск по VLAN и порту на устройстве.
После поиска сервиса можно дополнительно использовать поиск дочернего устройства (как элемент дополнительной авторизации).
<subServSearchMode> может принимать значения:
0 или отсутсвует - нет поиска дочернего сервиса
1 - поиск дочернего сервиса по MAC-адресу, если такого дочернего сервиса нет - ошибка авторизации;
2 - поиск дочернего сервиса по MAC-адресу, если такого дочернего сервиса нет - сессия будет привязана к родительскому сервису.
Процесс DHCP-авторизации состоит из двух запросов: DISCOVER и REQUEST. В первом запросе клиент запрашивает IP-адреса, какие ему могут предложить DHCP сервера. Во втором просит закрепить за ним конкретный IP-адрес. На DHCP-сервер биллинга попадают запросы с опцией 82, которая позволяет идентифицировать клиента. После идентификации клиента ему выдаётся IP-адрес. Идентификатором сессии при DHCP.82 авторизации выступает MAC-адрес клиента. Допускается одновременная инициализация нескольких сессий за одним портом коммутатора.
Выдача IP-адреса
Адрес сессии выделяется либо из диапазона, указанного в самом сервисе, либо, если он исчерпан или не указан - из пула, определённого в конфигурации устройства. Пул адресов устройства определяется параметром конфигурации dhcp.ipCategories=<cat_codes>, где <cat_codes> - id коды категорий ресурсов IP адресов через запятую. Например:
dhcp.ipCategories=
4
При ошибке авторизации DISCOVER-запросы будут игнорироваться, а на все REQUEST-запросы будет высылаться ответ DHCP_NAK. Для предотвращения нагрузки на DHCP-сервер постоянной обработкой запросов возможно определение пула фиктивных адресов, выдаваемых при ошибках авторизации. Пул определяется переменной конфигурации устройства dhcp.disable.ipCategories=<cat_codes>, где <cat_codes> - id коды категорий ресурсов IP адресов через запятую. Например:
dhcp.disable.ipCategories=
3
,
4
При превышении количества сессий сервиса над ограничением в его свойствах при включенном pool.error выдаются несколько адресов из этого пула, после чего DHCP-запросы игнорируются. Это сделано для невозможности исчерпания пула фиктивных адресов отправкой большого количества DHCP-запросов с разными MAC-адресами. Количество сессий сверх ограничения, для которых могут быть выданы фиктивные адреса задаётся переменной конфигурации dhcp.additionalUnauthorizedSessionCount.
Также адреса для динамической выдачи можно указать с помощью именованных пулов IP-адресов:
dhcp.ipPool=myWhitePool
dhcp.disable.ipPool=myGreyPool
Для выдачи адреса, в случае, если сервис не был найден (например, для предоставления гостевого доступа), необходимо создать договор с балансом меньше лимита и сервисом модуля Inet со статусом закрыт, а ID сервиса прописать в конфигурации устройства:
# ID фиктивного сервиса для неаутентифицированных сессий
dhcp.disable.servId=
1
Таким образом, сессии с ненайденным сервисом будут привязываться к указанному сервису.
Эту опцию можно использовать в схемах, когда клиента подключают изначально в какой-то новый порт, он получает фиктивный адрес, для него создается сессия на специальном сервисе. Далее он заходит в личный кабинет и через дополнительные действия создает сервис , скрипт находит его сессию на фиктивном договоре (например, по IP), берет с нее информацию (порт, MAC-адрес и т.п.) и создает уже обычный сервис на договоре этого абонента.
Если необходимо, чтобы адрес выдавался независимо от баланса/статуса/состояния, т.е. всегда, как при положительном балансе и открытом статусе договора, нужно указать параметр dhcp.disable.mode=1 или 2. При значении 1 InetAccess будет выдавать адрес всегда так, как если авторизация прошла успешно. Однако при необходимости переключить сессию из состояния отключена в подключена или наоборот будет выдан NAK, сессия завершится и создастся новая, при этом вызывая в обработчике активации сервисов onAccountingStop и onAccountingStart. При значении 2 при необходимости переключить состояние сессия не завершается (но connectionModify вызывается во всех трех случаях).
# Режим выдачи адреса при неудачной авторизации. 0 (по умолчанию) - выдает адрес согласно параметрам dhcp.disable.*,
# 1 - выдает адрес как при удачной авторизации (при изменении состояния выдается NAK, срабатывает onAccountingStop, выдает ACK, срабатывает onAccountingStart),
# 2 - выдает адрес как при удачной авторизации (при изменении состояния продолжает выдавать адрес).
dhcp.disable.mode=
0
В случае, если в сервисе или дочернем сервисе необходимо указать IP-адрес для управления, но при этом адрес из этого сервиса не должен выдаваться по DHCP, то в конфигурации типа сервиса следует указать:
# Не выдавать адрес по DHCP из сервисов данного типа
serv.dhcp.noAddress=
1
Выдача IP-адреса в зависимости от опций Inet
При необходимости выдачи IP-адресов из разных пулов в зависимости от опций Inet, необходимо описать именованный пул IP-адресов. и указать данный пул в конфигурации опции Inet:
# При активной опции на сервисе договора выдаем адрес из указанного пула IP-адресов
dhcp.ipPool=myWhitePool
Или в конфигурации устройства:
# При активной опции c ID 5 на сервисе договора выдаем адрес из указанного пула IP-адресов
dhcp.inetOption.
5
.ipPool=myWhitePool
Выдача DHCP-опций
Помимо IP-адреса в ответе DHCP-запроса могут передаваться различные опции. Их можно указать как в IP-ресурсе, так и конфигурации устройства. При указании в IP-ресурсе полей Роутер, Маска и DNS, значения, при выдаче адреса из этого ресурса соответственно попадут в опции gate, subnetMask и dns. Также в конфигурации IP-ресурса можно указать дополнительные опции с помощью параметров dhcp.option.<option_name>=<option_value>.
Задать опции в конфигурации устройства можно с помощью переменных конфигурации dhcp.option.<option_name>=<option_value> и dhcp.net.option.<net>.<mask>.<option_name>=<option_value>, где:
<net> - сеть, для которой выдаётся параметр;
<mask> - маска сети;
<option_name> - название опции;
<option_value> - значение опции.
Первый параметр устанавливает опции безусловно, второй - в зависимости от выданного IP-адреса.
Возможные значения названий опций и их значений перечислены в таблице.
Название опции |
Значение в виде |
В DHCP пакете |
leaseTime |
Число в секундах. |
Опция 51, срок аренды IP-адреса |
timeOffset |
Число в секундах. |
Опция 2. |
gate |
Строка с IP-адресом в виде NNN.NNN.NNN.NNN. |
Опция 3, маршрутизатор |
serverIdentifier |
Строка с IP-адресом в виде NNN.NNN.NNN.NNN. |
Опция 54, идентификатор DHCP-сервера |
dns |
Строка с одним или несколькими адресами вида NNN.NNN.NNN.NNN, разделённых запятой. |
Опция 6, DNS-сервера |
domainName |
Строка. |
Опция 15, домен |
subnetMask |
Строка с IP-адресом в виде NNN.NNN.NNN.NNN. |
Опция 1, маска подсети |
renewalTime |
Число в секундах. |
Опция 58, время, после которого DHCP-клиент должен перейти в RENEW |
rebindingTime |
Число в секундах. |
Опция 59, время, после которого DHCP-клиент должен перейти в REBIND |
Также можно добавить опцию с помощью ее кода/типа и указания значения в формате HEX, например, dhcp.option.43=68656c6c6f.
Для поддержки прямых RENEW запросов (т.е. когда RENEW запрос идет напрямую от абонента, не проходя через relay), в конфигурации нужно указать dhcp.renew=1 (см. Получение IP-адреса DHCP-клиентом). При этом для таких запросов можно указать специфичный набор опций, как dhcp.renew.option.<option_name>.
Пример конфигурации:
dhcp.option.serverIdentifier=
0.0
.
0.0
dhcp.option.leaseTime=
60
#
#dhcp.option.renewalTime=
#dhcp.option.rebindingTime=
#dhcp.inetOption.
1
.leaseTime=
120
#
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
Блокировка бесконечных DHCP-DISCOVER
Возможна ситуация, когда DHCP-клиент в роутере абонента повисает и начинает слать бесконечные DISCOVER. Чтобы постоянно не резервировать IP-адресы из пула предусмотрена система блокировки. Например, по умолчанию, если за две минуты пришло 10 DISCOVER-пакетов, то блокировать на полчаса. Рекомендуется менять только параметры dhcp.ban.discoverCount и dhcp.ban.timeout. Конфигурацию можно указывать в устройстве и типе устройства:
# На сколько действует выданный OFFER (не может быть больше
60
секунд)
dhcp.offer.timeout=
25
# промежуток времени в секундах, за которой пришло указанное кол-во DISCOVER
dhcp.ban.checkTimeout=
120
# кол-во DISCOVER, которое пришло за указанный промежуток времени
dhcp.ban.discoverCount=
10
# время в секундах, на которое перестаем отвечать на DISCOVER-запросы
dhcp.ban.timeout=
1800
При необходимости можно сбросить бан с помощью командной строки:
.
/access
.sh dhcpBanRemove
Действие при получении DHCP-Discover при наличии активной сессии
При работе по протоколу DHCP нельзя достоверно узнать, когда закончилась DHCP-сессия (доверять DHCP-Release мы не можем), поэтому в биллинге такая сессия по умолчанию завершается по таймауту connection.close.timeout, который выставляется больше, чем время выданное lease.
При смене абонентом устройства идет новый запрос DHCP-Discover, но может быть ситуация, когда старая сессия еще не закрыта по таймауту (пример - только что было продление адреса и абонент сменил устройство). В этом случае будет считаться, что у абонента уже есть одна сессия и что IP-адрес этой сессии еще занят. Для того, чтобы на DHCP-Discover происходило закрытие активных сессий на сервисе, нужно указать dhcp.connection.closeOnNew=1. Но в этому случае на одном сервисе возможна только одна активная сессия.
С версии 7.0 для обработки такой ситуации (вместо использования dhcp.connection.closeOnNew=1) можно использовать параметр конфигурации dhcp.connection.checkDuplicate. Он работает аналогично radius.connection.checkDuplicate, поэтому нужно указать цифру во втором разряде (для обработки ситуации, когда количество активных соединений превышено, но соединения с таким MAC-адресом не найдено, и нужно отключить просто самое старое соединение):
4 - происходит попытка сброса старого соединения, затем через 5 секунд завершение в биллинге, абонента на этой авторизации не пускаем;
5 - попытка сброса старого соединения в биллинге, завершение и пускаем немедленно (т.е. игнорируется ошибка );
6 - завершение старого соединения в биллинге, пускаем абонента немедленно;
7 - попытка в течении 5 секунд сбросить соединение, затем закрытие сессии в биллинге с ожиданием полного выполнения закрытия (т.е. IP-адрес станет свободным), пускаем абонента
8 - попытка сброса и сразу закрытие в биллинге с ожиданием полного выполнения закрытия (т.е. IP-адрес станет свободным), пускаем абонента;
9 - закрытие старого соединения в биллинге с ожиданием полного выполнения закрытия (т.е. IP-адрес станет свободным), пускаем абонента.
В большинстве случаев подойдет значение dhcp.connection.checkDuplicate=90.
Дополнительные параметры
В некоторых случаях возможна ситуация, когда запросы с неизвестных биллингу relay-агентов нужно игнорировать (т.к. в случае RENEW-запроса и dhcp.renew=1 биллинг будет отвечать NAK). Для этого в конфигурации корневого устройства укажите:
# Нужно ли игнорировать все запросы с неизвестным giaddr (требуется перезапуск InetAccess)
# 0 - нет (по умолчанию), 1 - да
dhcp.skipUnknownDevices=
1
При поиске relay-агента сначала происходит поиск по giaddr, затем, если устройство еще не найден - поиск по source IP-адресу DHCP-пакета. Если данный дополнительный поиск необходимо отключить, укажите в конфигурации корневого устройства:
# Поиск по IP-адресу дополнительно к поиску по giaddr (требуется перезапуск InetAccess)
# 0 - нет, 1 - да (по умолчанию)
dhcp.findDeviceBySocketAddress=
0
Если Вы не хотите указывать в дереве устройств все relay-агенты, а производить поиск только по agentRemoteId, то в конфигурации корневого устройства вы можете указать ID устройства, к которому будут привязываться DHCP-пакеты, вместо поиска по giaddr:
# Привязка всех DHCP-пакетов к устройству, вместо поиска по giaddr (требуется перезапуск InetAccess)
dhcp.overrideRelayDeviceId=