Слушатель InetDhcpListener

Предназначен для обработки DHCP-пакетов для схем DHCP.82, ISG/CLIPS+DHCP.82. Используется в InetAccess, добавляется в контейнер следующим образом:

Код
<context name="dhcp">
  <!-- Cоздание процессора dhcp-пакетов -->
  <bean name="dhcpProcessor" class="ru.avantis.abilling.modules.inet.dhcp.InetDhcpProcessor"/>

  <scheduledExecutorService name="hrlydtlggr" corePoolSize="1" />

  <!-- Cоздание dataLogger, сохраняющего dhcp-пакеты на диск  -->
  <bean name="dhcpDataLogger" class="ru.avantis.abilling.modules.inet.dhcp.DhcpHourlyDataLogger">
    <param name="scheduledExecutor">hrlydtlggr</param>
  </bean>

  <!-- Cоздание слушателя dhcp-пакетов на порту с передачей ему процессора и dataLogger -->
  <bean name="dhcpListener" class="ru.avantis.abilling.kernel.network.dhcp.DhcpListener">
    <constructor>
      <!-- Хост (интерфейс), на котором будет открыт сокет. Если пусто - на всех -->
      <param name="host" value=""/>
      <!-- Порт, на котором будет открыт сокет -->
      <param name="port" value="67"/>
      <!-- Размер буфера приема слушателя -->
      <param name="byteBufferCapacity">512 * 1024</param>
      <!-- Количество потоков-обработчиков -->
      <param name="threadCount">10</param>
      <!-- Максимальное количество пакетов в очереди на обработку -->
      <param name="maxQueueSize">200</param>
      <!-- Передача процессора -->
      <param name="processor">dhcpProcessor</param>
      <!-- Передача dataLogger -->
      <param name="dataLogger"></param>
    </constructor>
  </bean>
</context>

Параметры:

  • host - IP-адрес, на котором слушатель, пустое значение - прослушивать любой IP-адрес;

  • port - прослушиваемый UDP порт;

  • byteBufferCapacity - размер буфера для приёма пакетов;

  • threadCount - число потоков-обработчиков пакетов, рекомендуемые значения 10-50, не рекомендуется указывать более 100;

  • maxQueueSize - максимальный размер очереди пакетов, при превышении размера очереди пакеты начинают отбрасываться и высылается аларм. Рекомендуемые значения 200-1000. Следует учитывать, что если по какой-то причине сервер не успевает обрабатывать пакеты, очередь растет, то какие-то пакеты из очереди могут быть обработаны с опозданием и DHCP-клиент уже выслал повторный запрос, который опять попадет в очередь и опять может быть обработан с опозданием. Поэтому большое значение вместо распределения нагрузки может вызвать ее увеличение;

  • dataLogger - объект, осуществляющий запись бинарных логов, по умолчанию это объект типа ru.avantis.abilling.modules.inet.dhcp.DhcpHourlyDataLogger (сохранение бинарных логов с разбивкой по источникам и часам);

  • processor - процессор, реализующий логику обработки пакетов.

Процессор ru.avantis.abilling.modules.inet.dhcp.InetDhcpProcessor

Предназначен для выдачи IP-адресов по протоколу DHCP с опцией 82. Опцию в запрос должен подставить коммутатор, пропускающий запрос, далее запрос обязательно должен переслать DHCP-Relay. Идентификация сервисов осуществляется на основании полей circuitId и remoteId, но также могут быть настроены любые другие поля, определяющие порт коммутатора клиента.

Загружает список устройств-коммутаторов, начиная с корневого узла. Типы устройств-коммутаторов определяются в переменной конфигурации корневого узла dhcp.relay.deviceTypeIds через запятую. Также загружаются привязанные к коммутаторам сервисы договоров.

Когда приходит DHCP-запрос, из него извлекается поле giaddr (Relay-IP). Осуществляется поиск устройства-релея сначала по совпадению этого поля с адресом устройства в биллинге. Затем, если поиск был отрицательным - осуществляется поиск по совпадению IP-адреса, с которого пришёл DHCP-запрос с IP-адресом устройства.

Если по каким-то причинам клиентские устройства (например, NetGear JWNR2000) в DHCP-REQUEST посылают xid, отличный от DHCP-DISCOVER, можно убрать привязку к xid-запросам, прописав в конфигурации устройства-коммутратора/типа устройства/конфигурации модуля:

# Привязка к xid DHCP-запросов
# 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-адрес клиента. Допускается одновременная инициализация нескольких сессий за одним портом коммутатора.

Адрес сессии выделяется либо из диапазона, указанного в самом сервисе, либо, если он исчерпан - из пула, определённого в конфигурации устройства. Пул адресов устройства определяется параметром конфигурации 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.

Для выдачи адреса, в случае, если сервис не был найден (например, для предоставления гостевого доступа), необходимо создать договор с балансом меньше лимита и сервисом модуля Inet со статусом закрыт, а 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-запроса могут передаваться различные опции. Их можно указать как в 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-адреса.

Возможные значения названий опций и их значений перечислены в таблице.

Таблица опций

Для поддержки прямых RENEW запросов (т.е. когда RENEW запрос идет напрямую от абонента, не проходя через relay), в конфигурации нужно указать dhcp.renew=1. При этом для таких запросов можно указать специфичный набор опций, как 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 нельзя достоверно узнать, когда закончилась 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.

Процессор ru.avantis.abilling.modules.inet.dhcp.InetDhcpHelperProcessor

Предназначен для работы в связке с RADIUS-процессором. При успешной авторизиции или старте сессии InetDhcpHelperProcessor запоминает сессию по ключу, который создается из шаблона dhcp.key.pattern, указанного в конфиге. В шаблоне могут быть указаны следующие параметры:

  • deviceId - устройство, к которому привязана сессия, т.е. NAS, с которого идет RADIUS-аккаунтинг;

  • remoteId - агентское устройство, обычно коммутатор с которого пришел DHCP-relay-запрос на NAS;

  • circuitId - порт или VLAN, в зависимости от указанного типа поиска;

  • mac - MAC-адрес, для данной схемы он должен быть в RADIUS-атрибуте Calling-Station-Id.

При получении DHCP-запроса по указанному dhcp.key.pattern извлекаются данные из DHCP-пакета, т.е. по giaddr или IP-адресу, от которого запроса идет поиск устройства deviceId, аналогично InetDhcpProcessor из пакета извлекаются agentRemoteId и circuitId, по agentRemoteId идет поиск агентского устройства remoteId, и по circuitId - порта или VLAN, MAC-адрес извлекается из соответсвующего поля DHCP-пакета.

По этому ключу идет поиск среди запомненных RADIUS-сессий и выдача соответствующего ответа c IP-адресом из сессии.

Т.е. задача настроить так, чтобы из RADIUS-пакета и DHCP-пакета извлекались одни и те же данные (устройство, агентское устройство, порт/VLAN, MAC-адрес). Для этого в обработчике процессора протокола происходит извлечение из RADIUS-пакета DHCP-опций remoteId и circuitId.

Работа процессора зависит от параметра radius.servSearchMode:

  • при поиске по логину (не рекомендуется, устарел, не удобен для связки с DHCP), в логине пользователя должны содержаться разделёнными двоеточием субопции DHCP.82 remoteId и circuitId, в номере звонящего - строка с HEX MAC-адреса. IP-адрес запоминается по ключу код устройства + опции + MAC-адрес;

  • при остальных видах поиска из DHCP-запроса извлекаются идентификатор агентского устройства и номер порта или VLAN, аналогично InetDhcpProcessor. IP-адрес запоминается по ключу код устройства + код агентского устройства + номер интерфейса/VLAN + MAC.

Во всех случаях в RADIUS-атрибуте Calling-Station-Id должен быть проставлен MAC-адрес, чтобы при перезагрузке Access-сервера данные загрузились из сессий.

# Параметр привязки IP-адреса (сессии) к DHCP
dhcp.key.pattern=$deviceId:$remoteId:$circuitId:$mac
# Если по какой-то причине невозможно узнать из RADIUS-запроса агентское устройство или номер интерфейса/VLAN, то привязку можно сократить до кода устройства + MAC:
#dhcp.key.pattern=$deviceId:$mac

Процессор загружает список устройств-коммутаторов, начиная с корневого узла. Типы устройств-коммутаторов можно отфильтровать в переменной конфигурации корневого узла dhcp.relay.deviceTypeIds через запятую. Алгоритм поиска устройства при получении запроса идентичен ru.avantis.abilling.modules.inet.dhcp.InetDhcpProcessor.

Пример пакетов для обработчика ISGProtocolHandler:

Код
RADIUS:
Packet type: Access-Request
Identifier: 140
Authenticator: {12 6B D7 9F 37 1E A1 39 BA 8C CD 13 0B CD 98 7B}
Attributes:
  User-Name=00060012cf539f5e:000403420101:0080.4840.a46f
  NAS-Identifier=7201-ipoe.nettrans.ru
  NAS-Port-Id=0/0/1/834
  User-Password=123
  Event-Timestamp=1314183142
  NAS-IP-Address=94.125.95.117
  NAS-Port=412
  Service-Type=5
  Acct-Session-Id=720000000000019C
  NAS-Port-Type=33
  cisco-avpair=circuit-id-tag=000403420101
  cisco-avpair=remote-id-tag=00060012cf539f5e
  cisco-NAS-Port=0/0/1/834


DHCP:
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}

Пример конфигурации для таких пакетов:

Конфигурация
# Параметры извлечения опций из RADIUS-пакета
# игнорирование заголовка опций (длина)
radius.agent.option.removeHeader=2
# RADIUS-атрибут cisco-avpair
radius.agent.option.remoteId.type=1
# префикс для cisco-avpair с опцией remoteId
radius.agent.option.remoteId.prefix=remote-id-tag=
# RADIUS-атрибут cisco-avpair
radius.agent.option.circuitId.type=1
# префикс для cisco-avpair с опцией circuitId
radius.agent.option.circuitId.prefix=circuit-id-tag=

# Параметры извлечения опций из DHCP-пакета
# игнорирование заголовка опций (длина)
dhcp.option82.removeHeader=2
# Параметры для извлечения из пакета agentRemoteId
# код субопции 82, содержащей идентификатор коммутатора клиента, позиция и длина последовательности идентификатора
dhcp.option82.agentRemoteId.code=2
dhcp.option82.agentRemoteId.position=0
dhcp.option82.agentRemoteId.length=6

# Параметры извлечения INTERFACE_ID и VLAN
# (position и length используются и для извлечения их из circuitId, найденном в RADIUS-пакете!)
# код субопции 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

# Шаблон, по которому создается ключ для привязки RADIUS-сессии и DHCP-пакетов.
# $deviceId - устройство (обычно NAS и relay-агент, с которого пришел DHCP-запрос на биллинг)
# $remoteId - агенское устройство (обычно коммутатор, к которому подключен абонент), определенное по agentRemoteId
# $circuitId - в зависимости от типа поиска сервиса либо interfaceId, либо VLAN
# $mac - MAC-адрес
radius.key.pattern=$deviceId:$remoteId:$circuitId:$mac

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

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

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

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

# Нужно указать код субопции опции 82 для извлечения значений
# agentRemoteId обычно находится в субопции 2
dhcp.option82.agentRemoteId.code=2
# interfaceId обычно находится в субопции 1 (circuitId)
dhcp.option82.interfaceId.code=1
# vlanId обычно находится в субопции 1 (circuitId)
dhcp.option82.vlanId.code=1
# Если в субопции отсутствует заголовок с длиной субопции, то укажите 0. Иначе укажите длину заголовка.
# Данный параметр используется в том числе, для того, чтобы извлеченные значения circuitId из DHCP-пакета и из RADIUS-пакета были идентичны.
# Соответственно, значение position нужно указывать относительно removeHeader.
dhcp.option82.removeHeader=0
# Нужно указать параметры извлечения agentRemoteId из субопции (dhcp.option82.agentRemoteId.code)
# позиция значения внутри субопции
dhcp.option82.agentRemoteId.position=2
# если длина значения может изменятся и нужно брать значение до конца субопции, то укажите -1
dhcp.option82.agentRemoteId.length=6
# 0, если remoteId в бинарном виде, например, MAC-адрес; 1, если там закодирована строка
dhcp.option82.agentRemoteId.type=0
# Нужно указать параметры извлечения interfaceId из субопции (dhcp.option82.interfaceId.code)
dhcp.option82.interfaceId.position=5
dhcp.option82.interfaceId.length=1
# Нужно указать параметры извлечения interfaceId из субопции (dhcp.option82.vlanId.code)
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, в отличие от варианта выше. Поэтому нужно воспользоваться предобработкой пакетов. Укажите тип поиска DHCP-устройства = 0 (в этом режиме сначала находится устройство по giaddr, у него вызывается preprocess, затем находится агентское ус-во, у него тоже вызывается preprocess).

dhcp.deviceSearchMode=0

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

import java.util.Arrays;

import ru.avantis.abilling.kernel.network.dhcp.DhcpPacket;
import ru.avantis.abilling.kernel.network.dhcp.DhcpProtocolHandler;
import ru.avantis.abilling.modules.inet.access.sa.ProtocolHandlerAdapter;
import ru.avantis.abilling.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.avantis.abilling.kernel.network.radius.RadiusAttribute;
import ru.avantis.abilling.kernel.network.radius.RadiusPacket;
import ru.avantis.abilling.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.avantis.abilling.kernel.network.dhcp.DhcpPacket;
import ru.avantis.abilling.kernel.network.radius.RadiusAttribute;
import ru.avantis.abilling.kernel.network.radius.RadiusPacket;
import ru.avantis.abilling.modules.inet.dhcp.InetDhcpProcessor;
import ru.avantis.abilling.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’а нужно указать отдельный обработчик, см. выше.

Получение IP-адреса DHCP-клиентом

Для корректной работы абонента требуется передавать в OFFER/RESPONSE, кроме выданного IP-адреса, как минимум опции serverIdentifier (идентификатор DHCP-сервера), gate (шлюз), subnetMask (маска подсети), leaseTime (время в секундах, на которое выдаем IP-адрес), dns (DNS-сервера в порядке важности).

dhcp.option.serverIdentifier=10.0.6.56
dhcp.option.leaseTime=600
#
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.subnetMask=255.255.255.0
dhcp.net.option.193.106.88.0:255.255.255.0.dns=194.165.18.6

В опции serverIdentifier обычно нужно указать IP-адрес InetAccess; в некоторых случаях - 0.0.0.0 или адрес Cisco ASR.

Опции gate, subnetMask, dns можно указать в IP-ресурсе в соответствующих полях, а не через конфигурацию устройства. Также в конфигурации IP-ресурса можно указать опции, привязанные к этому ресурсу, аналогично конфигурации устройства, через префикс dhcp.option.

Важно учитывать что при истечении времени T1 (или по другому - renewal time) DHCP-клиент переходит в стадию RENEW и начинает отправлять запросы RENEW на продление lease. Если ответа он не получает, то при истечении времени T2 (или по другому - rebinding time) DHCP-клиент переходит в стадию REBIND и отправляет REBIND-запросы для продления lease выданного ранее IP-адреса. Время T1 по умолчанию - это (leaseTime * 0.5), T2 - это (leaseTime * 0.875).

Существуют опции renewalTime и rebindingTime, с помощью которых можно указать конкретные значения, когда DHCP-клиент должен перейти в стадию RENEW, а когда в REBIND. Значение renewalTime должно быть меньше rebindingTime, а значение rebindingTime - меньше leaseTime.

dhcp.option.leaseTime=600
dhcp.option.renewalTime=180
dhcp.option.rebindingTime=420

По умолчанию сервер InetAccess не отвечает на RENEW-запросы (чтобы заставить DHCP-клиент перейти в стадию REBIND), но это работает далеко не всегда и далеко не во всех схемах. Чтобы включить обработку RENEW-запросов, в конфигурации корневого устройства (обычно это Access+Accounting) нужно указать (требуется перезапуск InetAccess):

dhcp.renew=1

Существует множество реализаций DHCP-клиентов и не все они работают согласно RFC.

Например, по RFC DHCP-клиент должен посылать DISCOVER и REQUEST из одной сессии запроса IP-адреса с одинаковым xid (Transaction ID), но некоторые DHCP-клиенты в REQUEST, отправляют другой xid (причем isc-dhcp-server обрабатывает это нормально, т.е. не смотрит на xid). Чтобы InetAccess не связывал DISCOVER и REQUEST по xid и обрабатывал такие запросы нормально, в конфигурации корневого устройства (обычно это Access+Accounting) нужно указать (требуется перезапуск InetAccess):

dhcp.xid=0

В конфигурации isc-dhcp-server есть параметр always-broadcast:

Цитата
always-broadcast flag;

The DHCP and BOOTP protocols both require DHCP and BOOTP clients to set the broadcast bit in the flags field of the BOOTP message header. Unfortunately, some DHCP and BOOTP clients do not do this, and there- fore may not receive responses from the DHCP server. The DHCP server can be made to always broadcast its responses to clients by setting this flag to 'on' for the relevant scope; relevant scopes would be inside a conditional statement, as a parameter for a class, or as a parameter for a host declaration. To avoid creating excess broadcast traffic on your network, we recommend that you restrict the use of this option to as few clients as possible. For example, the Microsoft DHCP client is known not to have this problem, as are the OpenTransport and ISC DHCP clients.

Аналогично этому параметру, в конфигурации устройства (не обязательно корневого) можно указать (требуется нажать "Перечитать конфигурацию на серверах"):

dhcp.alwaysBroadcast=1