Inet FAQ
Материал из BiTel WiKi
Amir (Обсуждение | вклад) |
Amir (Обсуждение | вклад) (→Свободный IP-адрес не найден или Can't reserve ip address) |
||
Строка 136: | Строка 136: | ||
- | == | + | == Все сессии сбросились на NAS'е, при массовом переподключении - проблемы == |
- | + | Если произошел сбой питания или по какой-то другой причине сессии на NAS'е были сброшены - в этой ситуации сессии остаются в биллинге, т.к. от NAS'а не пришел RADIUS-stop-пакет. При этом адрес, привязанный к сессии считается занятым. По умолчанию сессия не будет закрыта, пока не выйдет connection.close.timeout. Также следует учитывать, что перезапуск InetAccounting приведет к сбросу последней активности на время запуска InetAccess (т.к. в другой ситуации пакеты могли приходить пока InetAccess был не запущен). При этом могут возникнуть проблемы, что абонентов не пускает по ограничению кол-ва сессий, либо происходит ошибка "Свободный IP-адрес не найден". | |
+ | Такие повисшие сессии можно убрать двумя способами: | ||
+ | |||
+ | 1. С помощью radius.connection.checkDuplicate http://bgbilling.ru/v6.1/doc/ch17s13s03s01.html , чтобы при новых Access-Request от одного и того же пользователя старую сессию закрывать в биллинге. Желательно чтобы и при обычной работе этот параметр был указан (но скорее всего понадобится другое значение, чем при такой аварии). | ||
+ | |||
+ | 2. Уменьшить в конфигурации устройства connection.close.timeout и connection.suspend.timeout до минуты или даже 5 секунд, указать в конфигурации корневого устройства более частую попытку завершения сессии http://bgbilling.ru/v6.1/doc/ch17s07s01.html: | ||
+ | # пауза между заданиями | ||
+ | accounting.worker.3.finishing.1.delay=10 | ||
+ | # максимальное количество сброшенных соединений в базу за задание | ||
+ | accounting.worker.3.finishing.1.batchSize=1000. | ||
+ | Перезапустить InetAccounting, затем после того как сессии будут завершены в биллинге и пропадут из монитора вернуть правильные значения connection.close.timeout и connection.suspend.timeout и нажать "Перечитать конфигурацию на серверах". Позже можно вернуть сброс сессий в БД (finishing) в прежние значения (для этого понадобится перезапуск InetAccounting). | ||
+ | |||
+ | 3. Скоро планируем добавить пункт меню для устройства "Завершить все сессии на устройстве". | ||
+ | |||
+ | |||
+ | Также следует учитывать, что при выдаче Access-Accept вместе с ним выдается IP-адрес, который резервируется на минуту. Таким образом, если в inet-access.xml для radiusListener указано большое значение очереди maxQueueSize возможна ситуация, что InetAccess будет выдавать Access-Accept, который абонент уже перестал ждать и отправил новый Access-Request. В этом случае адреса будут выдаваться в никуда, это также может быть причиной ошибки "Свободный IP-адрес не найден", а также задержке восстановления до нормальной работы. Поэтому если maxQueueSize больше 200, укажите 100-200 и перезапустите InetAccess. С версии 6.0 InetAccess старается не обрабатывать пакеты из очереди, которые он считает устаревшими, поэтому вероятность возникновения проблемы из-за maxQueueSize меньше. | ||
== Access и/или Accounting потребляют много памяти, постепенно после старта == | == Access и/или Accounting потребляют много памяти, постепенно после старта == |
Версия 08:49, 24 октября 2014
Не запускается Access и/или Accounting
Вы только установили Access/Accounting и в access.out или accounting.out ошибка java.lang.ClassNotFoundException, например, для Access:
Error on node access
java.lang.ClassNotFoundException: ru.bitel.bgbilling.modules.inet.access.Access
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:190)
at ru.bitel.bgbilling.kernel.application.server.Application.processBean(Application.java:391)
at ru.bitel.bgbilling.kernel.application.server.Application.process(Application.java:297)
at ru.bitel.bgbilling.kernel.application.server.Application.processChildren(Application.java:749)
at ru.bitel.bgbilling.kernel.application.server.Application.application(Application.java:225)
at ru.bitel.bgbilling.kernel.application.server.Application.<init>(Application.java:161)
at ru.bitel.bgbilling.kernel.application.server.Application.main(Application.java:803)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at ru.bitel.common.bootstrap.Boot.boot(Boot.java:129)
at ru.bitel.common.bootstrap.Boot.main(Boot.java:178)
В документации по установке Access/Accounting указан пункт "Обновитe как обычные серверные приложения биллинга", т.е. запустите update.sh, который находится в директории биллинга.
Access/Accounting не обновляются через update.sh
При вызове update.sh у Access/Accounting происходит ошибка TimeoutException или NullPointerException:
java.lang.NullPointerException at bitel.billing.server.installer.library.LibraryUpdate.getLibrariesForUpdate(LibraryUpdate.java:94) at bitel.billing.server.installer.library.LibraryUpdate.main(LibraryUpdate.java:119)
Проверьте, что BGBillingServer запущен.
Если Access/Accounting и BGBillingServer запущены на разных машинах - проверьте что время на этих машинах синхронизировано.
Соединение с ограниченным доступом (Reject-To-Accept) подключается, но почти сразу отключается или постоянно меняется ограничение соединения
Обычно это происходит из-за того, что Accounting не знает, в каком состоянии соединение, "подключен" или "отключено", т.е. полный доступ или ограниченный (например, редирект). За это отвечают несколько параметров:
# при значении 1 перед выдачей Access-Accept InetAccess заносит запись в БД об этом соединении (рекомендуется) connection.start.fromAccept=1 # состояние соединения можно определить по наличию определенных атрибутов в Accounting-пакетах (не рекомендуется) radius.disable.pattern.attributes= # при значении 1 InetAccess при вызове для изменения состояния метода connectionModify из CoAServiceActivator/ISGServiceActivator/SmartEdgeServiceActivator сразу поменяет состояние в БД (вызовет e.setConnectionStateModified( true ), рекомендуется для CoAServiceActivator) sa.radius.connection.stateModify=1 # для схем ISG, SmartEdge с посервисным аккаунтингом состояние сессии можно определять по активности сервиса ISG/SmartEdge (рекомендуется для схем с посервисным аккаунтингом) radius.serviceName.disable=
Рекомендуемые параметры для CoAServiceActivator:
connection.start.fromAccept=1 sa.radius.connection.stateModify=1
Рекомендуемые параметры для ISGServiceActivator/SmartEdgeServiceActivator c посервисным аккаунтингом:
connection.start.fromAccept=1 sa.radius.connection.stateModify=0 radius.serviceName.disable=<сервис(ы) с ограниченным доступом, через запятую>
Глючит, абонентов не пускает, команды на коммутаторах не выполняет
Возможно в ActiveMQ накопилось большое кол-во сообщений и нет возможности их корректно обработать. Проверьте, если в /opt/activemq/data/kahadb большое кол-во файлов *.log (например, больше 5-ти), то скорее всего причина в этом.
Удаление определенных сообщений из очереди ActiveMQ
Для того, чтобы через командную строку удалить сообщения, необходимо, чтобы в ActiveMQ был включен JMX. Для этого в conf/activemq.xml должен был пункт managementContext с createConnector="true"
<managementContext> <managementContext createConnector="true"/> </managementContext>
После изменения необходимо перезапустить ActiveMQ.
Если JMX включен, то команда activemq-admin purge позволяет удалить определенные сообщения из очереди. Например, чтобы удалить сообщения о необходимости изменения состояния сервисов для устройства с ID=55, нужно выполнить:
./activemq-admin purge --msgsel "deviceId=55" BG.Event.ru.bitel.bgbilling.modules.inet.access.sa.event.InetSaStateModifyEvent
В ActiveMQ копятся и в итоге не обрабатываются сообщения
Для того, чтобы быстрее узнать, почему ActiveMQ копит сообщения лучше включить web-консоль ActiveMQ. Для этого в /opt/activemq/conf/activemq.xml расскомментируйте ветку <import resource="jetty.xml"/> и перезапусите ActiveMQ.
<!-- Enable web consoles, REST and Ajax APIs and demos It also includes Camel (with its web console), see ${ACTIVEMQ_HOME}/conf/camel.xml for more info Take a look at ${ACTIVEMQ_HOME}/conf/jetty.xml for more details --> <import resource="jetty.xml"/>
Откройте в браузере web-консоль http://адрес:8161/admin, перейдите в Queues. Number Of Pending Messages - это кол-во сообщений, которые ActiveMQ получил, но еще не успел или не смог передать приложениям биллинга. При нормальной работе среднее значение 0 или небольшое, т.е. при увеличении очень быстро уменьшается обратно.
Если web-консоль не открывается, при этом activemq.xml поправили корректно и порт 8161 открыт - возможно проблема в дистрибутиве activemq (например, файлы дистрибутива повреждены), попробуйте заменить его.
Если значения Number Of Pending Messages у каких-либо очередей большие у не уменьшаются, то на это может быть несколько причин:
- произошел какой-то сбой (например, ниже), очередь переполнилась и теперь не может отработать корректно;
- одно из устройств (NAS' или коммутатор) очень долгое время не отвечало при попытках синхронизации, таким образом сообщения для него постепенно копились и см. первую причину, нужно посмотреть логи Access;
- Access долгое время не был запущен или повис из-за какого-то сбой и см. первую причину;
- конфигурация Access некорректна, например, значения rootDeviceId или accounting.deviceTypeIds неправильные.
- ошибка в ServiceActivator при синхронизации с NAS'ом или коммутатором, нужно смотреть логи Access (возможно нужно перекомпилировать динамические классы);
- одно из устройств удалили, но к нему были привязаны активные сервисы Inet, в итоге Accounting генерирует сообщения, которые Access не может обработать.
Можно открыть конкретную очередь в web-консоли и посмотреть, какие там сообщения, для каких устройств (поле deviceId).
Решение:
- Если в Access постоянные ошибки с синхронизацией для всех устройств (т.е. даже если очередь заработает - функциональность не восстановится), попробуйте их исправить.
- Попробуйте перезапустить Access и Accounting.
- Если после перезапуска Access не начал выполнять синхронизацию или начал выполнять нормально, но очень и очень медленно, т.е. Number Of Pending Messages не уменьшается, можно попробовать очистить очередь, нажав на ссылку Purge для очереди в web-интерфейсе.
- Если Purge не выполняется вообще или выдает ошибку, можно попробовать удалить очередь через ссылку Delete, но после успешного удаления очереди или очередей нужно будет перезапустить BGBillingServer, BGInetAccess и BGInetAccounting.
- Если Delete тоже не выполняется или web-интерфейс не открывается, можно попробовать очистить данные activeMQ. Для этого нужно остановить BGBillingServer, Accounting, Access, ActiveMQ, переименовать папку /opt/activemq/data/kahadb (чтобы осталась резервная копия), запустить ActiveMQ, BGBillingServer, Access, Accounting.
- Если после очистки очередь все равно набирается - возможно проблемы с конфигурацией Access.
- Если с Access все в порядке, а за ActiveMQ замечены еще какие-то проблемы - возможно проблема в дистрибутиве activemq (например, файлы дистрибутива повреждены), попробуйте заменить его.
Таким образом, можно быстро решить проблему, очистив очередь одним из способов, но это, возможно, будет временное решение, если существует исходная проблема, из-за которой растет кол-во сообщений в очереди.
Не совпадает состояние сервиса Inet с тем, что должно быть
После сбоя или после ручного добавления платежей в БД состояние сервисов может не совпадать с необходимым. При этом при пересохранении сервиса состояние меняется на нормальное. Если это произошло по неизвестной причине, следует ее выяснить. Например, при проблеме c обработкой сообщений необходимо сначала устранить ее. Или есть скрипт добавления платежей, который не создает необходимые события - необходимо сначала его исправить.
Для того, чтобы заставить биллинг синхронизировать состояние сервисов согласно балансу нужно добавить в "Глобальные скрипты поведения" - "Классы Java" класс ru.bitel.bgbilling.modules.inet.dyn.access.InetServStateSync. Перед запуском этого скрипта нужно перезапустить BGBillingServer, чтобы синхронизация гарантировано запустилась для всех договоров.
Все сессии сбросились на NAS'е, при массовом переподключении - проблемы
Если произошел сбой питания или по какой-то другой причине сессии на NAS'е были сброшены - в этой ситуации сессии остаются в биллинге, т.к. от NAS'а не пришел RADIUS-stop-пакет. При этом адрес, привязанный к сессии считается занятым. По умолчанию сессия не будет закрыта, пока не выйдет connection.close.timeout. Также следует учитывать, что перезапуск InetAccounting приведет к сбросу последней активности на время запуска InetAccess (т.к. в другой ситуации пакеты могли приходить пока InetAccess был не запущен). При этом могут возникнуть проблемы, что абонентов не пускает по ограничению кол-ва сессий, либо происходит ошибка "Свободный IP-адрес не найден".
Такие повисшие сессии можно убрать двумя способами:
1. С помощью radius.connection.checkDuplicate http://bgbilling.ru/v6.1/doc/ch17s13s03s01.html , чтобы при новых Access-Request от одного и того же пользователя старую сессию закрывать в биллинге. Желательно чтобы и при обычной работе этот параметр был указан (но скорее всего понадобится другое значение, чем при такой аварии).
2. Уменьшить в конфигурации устройства connection.close.timeout и connection.suspend.timeout до минуты или даже 5 секунд, указать в конфигурации корневого устройства более частую попытку завершения сессии http://bgbilling.ru/v6.1/doc/ch17s07s01.html:
# пауза между заданиями accounting.worker.3.finishing.1.delay=10 # максимальное количество сброшенных соединений в базу за задание accounting.worker.3.finishing.1.batchSize=1000.
Перезапустить InetAccounting, затем после того как сессии будут завершены в биллинге и пропадут из монитора вернуть правильные значения connection.close.timeout и connection.suspend.timeout и нажать "Перечитать конфигурацию на серверах". Позже можно вернуть сброс сессий в БД (finishing) в прежние значения (для этого понадобится перезапуск InetAccounting).
3. Скоро планируем добавить пункт меню для устройства "Завершить все сессии на устройстве".
Также следует учитывать, что при выдаче Access-Accept вместе с ним выдается IP-адрес, который резервируется на минуту. Таким образом, если в inet-access.xml для radiusListener указано большое значение очереди maxQueueSize возможна ситуация, что InetAccess будет выдавать Access-Accept, который абонент уже перестал ждать и отправил новый Access-Request. В этом случае адреса будут выдаваться в никуда, это также может быть причиной ошибки "Свободный IP-адрес не найден", а также задержке восстановления до нормальной работы. Поэтому если maxQueueSize больше 200, укажите 100-200 и перезапустите InetAccess. С версии 6.0 InetAccess старается не обрабатывать пакеты из очереди, которые он считает устаревшими, поэтому вероятность возникновения проблемы из-за maxQueueSize меньше.
Access и/или Accounting потребляют много памяти, постепенно после старта
Возможно задействовано много источников логов (NAS'ы, которые посылают пакеты RADIUS, коммутаторы, которые посылают DHCP-пакеты, flow-агенты). При получения, обработки и записи DHCP/Radius/Netflow/sFlow пакетов используются буферы. Максимальное кол-во памяти, которые могут забрать буферы - threadCount * datalog.chunk.size * (кол-во устройств-источников данных), где threadCount - кол-во потоков слушателя (InetRadiusListener/DhcpListener/InetFlowListener).
В этом случае не стоит указывать большое кол-во потоков для слушателя threadCount и при большом кол-ве источников логов имеет смысл уменьшить chunk.size:
# Общее для всех значение, используется, если не указано специально для типа лога datalog.chunk.size=131072 # DHCP datalog.dhcp.chunk.size=65536 # RADIUS datalog.radius.chunk.size=65536 # Netflow/sFlow datalog.flow.chunk.size=262144
<!-- Параметры сохранения radius-пакетов в файлы логов --> <!-- Директория, в которую сохранять radius логи --> <param name="datalog.radius.dir" value="data/radius" /> <!-- Размер блока данных в файле лога, также размер буфера на поток слушателя --> <param name="datalog.radius.chunk.size" value="524288" /> <!-- Сжимать radius логи: 0 - не сжимать, 1 - zlib --> <param name="datalog.radius.compression.type" value="1" /> <!-- Параметры сохранения flow-пакетов в файлы логов --> <!-- Директория, в которую сохранять flow логи --> <param name="datalog.flow.dir" value="data/flow" /> <!-- Размер блока данных в файле лога, также размер буфера на поток слушателя --> <param name="datalog.flow.chunk.size" value="524288" /> <!-- Сжимать flow логи: 0 - не сжимать, 1 - zlib --> <param name="datalog.flow.compression.type" value="1" />
Как помочь разработчикам быстрее исправить ошибку
- Предоставить более полный лог ошибки (исключения), со всеми Caused, а не только первую строчку, например:
05-20/12:22:29 ERROR ["http-bio-/0.0.0.0-8080"-exec-5] AbstractJaxWsHandler - java.util.concurrent.ExecutionException: java.security.PrivilegedActionException: java.lang.Exception: java.lang.NullPointerException at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:232) at java.util.concurrent.FutureTask.get(FutureTask.java:91) at ru.bitel.bgbilling.kernel.script.server.DynamicScriptEventListener$ThreadedScriptEventListener.runScriptImpl(DynamicScriptEventListener.java:267) at ru.bitel.bgbilling.kernel.script.server.DynamicScriptEventListener.runScript(DynamicScriptEventListener.java:149) at ru.bitel.bgbilling.kernel.script.server.DynamicScriptEventListener.notify(DynamicScriptEventListener.java:117) at ru.bitel.bgbilling.kernel.event.LocalEventProcessor.request(LocalEventProcessor.java:240) at ru.bitel.bgbilling.kernel.event.EventProcessor.request(EventProcessor.java:848) at ru.bitel.bgbilling.kernel.event.EventProcessor.request(EventProcessor.java:817) at bitel.billing.server.contract.action.ActionUpdateListParam.doAction(ActionUpdateListParam.java:35) at bitel.billing.server.Executer.doModule(Unknown Source) at bitel.billing.server.Executer$1.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at javax.security.auth.Subject.doAs(Subject.java:396) at bitel.billing.server.Executer.doPost(Unknown Source) at javax.servlet.http.HttpServlet.service(HttpServlet.java:641) at javax.servlet.http.HttpServlet.service(HttpServlet.java:722) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at bitel.billing.server.filters.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:48) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:164) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:462) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:563) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:403) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:301) at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:162) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:309) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) at java.lang.Thread.run(Thread.java:662) Caused by: java.security.PrivilegedActionException: java.lang.Exception: java.lang.NullPointerException at java.security.AccessController.doPrivileged(Native Method) at javax.security.auth.Subject.doAs(Subject.java:396) at ru.bitel.bgbilling.kernel.script.server.DynamicScriptEventListener$ThreadedScriptEventListener$1.call(DynamicScriptEventListener.java:241) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) at java.lang.Thread.run(Thread.java:662) at ru.bitel.common.worker.WorkerThread.run(WorkerThread.java:40) Caused by: java.lang.Exception: java.lang.NullPointerException at ru.bitel.bgbilling.kernel.script.server.DynamicScriptEventListener.runScriptImpl(DynamicScriptEventListener.java:200) at ru.bitel.bgbilling.kernel.script.server.DynamicScriptEventListener$ThreadedScriptEventListener.access$0(DynamicScriptEventListener.java:1) at ru.bitel.bgbilling.kernel.script.server.DynamicScriptEventListener$ThreadedScriptEventListener$1$1.run(DynamicScriptEventListener.java:247) ... 9 more Caused by: java.lang.NullPointerException at ru.bitel.bgbilling.kernel.script.server.DynamicScriptEventListener.runScriptImpl(DynamicScriptEventListener.java:196) ... 11 more
- Сохранить лог-файлы на момент ошибки
- Сохранить информацию по стекам c помощью утилиты jstack из JDK:
jstack <id_процесса> > stack1.txt
- Если проблема сложная или связана с излишнем потреблением памяти - сделать дамп памяти процесса (может быть затратно по ресурсам) с помощью утилиты jmap из JDK (дамп памяти нельзя выкладывать в открытый доступ). Данная операция может быть ресурсозатратна, особенно на приложениях, которые заняли более 1GB оперативной памяти:
jmap -dump:format=b,file=dump.hprof <id_процесса>
Перед снятием дампа рекомендуется вызвать garbage collector:
accounting.sh gc
Как SQL запросом посмотреть IP-адрес, MAC-адрес из inet_serv varbinary(64)?
macAddres:SELECT HEX(macAddress) FROM inet_serv
SELECT INET_NTOA(CONV(HEX(ipAddress), 16, 10)) FROM inet_connection SELECT * FROM inet_connection WHERE ipAddress=UNHEX(CONV(INET_ATON('10.0.0.1'), 10, 16))
Невозможно удалить сервис с активным периодом действия
Нельзя удалять сервис, период действия которого пересекается с сегодняшним днем. Нужно закрыть сервис датой, например, хотя бы вчерашним днем, сохранить, потом удалить.
Создание Web-сервиса в динамическом коде
Тут создается Web-сервис InetDeviceService. Web-сервис для любого другого класса получается по аналогии. INET_MODULE_ID - код экземпляра модуля inet.
ServerContext context = ServerContext.get(); InetDeviceService wsDevice = context.getService( InetDeviceService.class, INET_MODULE_ID );
При работе по DHCP и статическом адресе абонент иногда получает динамический адрес
Скорее всего это происходит, когда абонент подключает другое устройство. Появляется новый MAC, биллинг не может технически определить, что предыдущая сессия уже закончилась, только по таймауту DHCP-lease, поэтому считает адрес еще занятым и выдает адрес из динамического пула. При схеме один порт/VLAN - один компьютер/роутер поможет параметр конфигурации устройства Inet dhcp.connection.closeOnNew=1. С этим флагом при появлении DHCP Discover с того же порта/VLAN с другим MAC-адресом предыдущая сессия будет завершена, таким образом адрес освободится и для новой сессии будет выдан он же.