Ограничение доступа на основе объектов
Материал из BiTel WiKi
ВНИМАНИЕ! Cтатья еще не закончена!
Ограничение доступа пользователей на основе объектов, использующих информацию из ADSL-Agent-Circuit-Id (RFC 4679).
Использование данной технологии позволяет так сказать "привязать" пользователя к порту свича и/или DSLAM без использования ограничений по МАС адресам и т.п.
Ниже рассмотрен случай с "привязкой" к IP адресу свича и номеру порта с которого осужествляется выход в сеть.
Как это выглядит в договоре:
- Объект абонента, для которого получен Circuit Id.
- Объект абонента, для которого Circuit Id не получен.
Содержание |
Предварительная настройка.
- Создаем тип объекта.
Значение имеет только макрос имени, должен быть: ${list:1}:${list:2}. Название значения не имеет (в данном случае: Ethernet):
- Создаем параметры объекта "IP адрес свича" (или, например, "IP адрес DSLAM") и "Номер порта".:
"Тип параметра" обязательно должен быть "список", т.к. в коде обрабатываются именно списки, а не текст, дата или адрес!
- Привязываем параметры, созданные в п.2 к типу объекта из п.1:
- Заполняем списки параметров:
- IP адрес свича:
Обязательно впишите IP адрес 0.0.0.0, т.к. этот IP адрес будет использован в случае если в атрибуте ADSL-Agent-Circuit-Id не будет IP адреса.
В списке должны быть перечислены IP адреса всех свичей, с которых Вы планируете получать информацию о Circuit-Id абонента.
- Номер порта:
Обязательно впишите номер порта 00, т.к. это номер порта будет использован в случае если в атрибуте ADSL-Agent-Circuit-Id не будет номера порта.
В списке должны быть перечислены все порты, с которых Вы планируете получать информацию о Circuit-Id абонента,:
т.е. если у вас на доступе стоят свичи в которых 24 порта, то Вы перечисляете их все с 1-го по 24-й, как это показано на картинке.
- IP адрес свича:
Словарь RADIUS-а:
<vendor name="ADSL-Forum" code="3561"> <attribute name="ADSL-Agent-Circuit-Id" code="1" type="string" /> <attribute name="ADSL-Agent-Remote-Id" code="2" type="string" /> <attribute name="Actual-Data-Rate-Upstream" code="129" type="integer" /> <attribute name="Actual-Data-Rate-Downstream" code="130" type="integer" /> <attribute name="Minimum-Data-Rate-Upstream" code="131" type="integer" /> <attribute name="Minimum-Data-Rate-Downstream" code="132" type="integer" /> <attribute name="Attainable-Data-Rate-Upstream" code="133" type="integer" /> <attribute name="Attainable-Data-Rate-Downstream" code="134" type="integer" /> <attribute name="Maximum-Data-Rate-Upstream" code="135" type="integer" /> <attribute name="Maximum-Data-Rate-Downstream" code="136" type="integer" /> <attribute name="Minimum-Data-Rate-Upstream-Low-Power" code="137" type="integer" /> <attribute name="Minimum-Data-Rate-Downstream-Low-Power" code="138" type="integer" /> <attribute name="Maximum-Interleaving-Delay-Upstream" code="139" type="integer" /> <attribute name="Actual-Interleaving-Delay-Upstream" code="140" type="integer" /> <attribute name="Maximum-Interleaving-Delay-Downstream" code="141" type="integer" /> <attribute name="Actual-Interleaving-Delay-Downstream" code="142" type="integer" /> <attribute name="Access-Loop-Encapsulation" code="144" type="octets" /> <attribute name="IWF-Session" code="252" type="octets" /> </vendor>
Скрипт предобработки:
import bitel.billing.server.radius.*; import java.util.*; import java.util.regex.*; /* удаляем атрибуты RFC 2865 (http://tools.ietf.org/html/rfc2868) т.к. у нас они реально не используются, а в логах только место занимают :) */ if (request.hasAttribute(64)) request.removeAttributes(64); // Tunnel-Type if (request.hasAttribute(65)) request.removeAttributes(65); // Tunnel-Medium-Type if (request.hasAttribute(66)) request.removeAttributes(66); // Tunnel-Client-Endpoint if (request.hasAttribute(90)) request.removeAttributes(90); // Tunnel-Client-Auth-ID if (request.hasAttribute(91)) request.removeAttributes(91); // Tunnel-Server-Auth-ID // атрибут Called-Station-Id меня не интересует - удаляем if (request.hasAttribute(30)) request.removeAttributes(30); /* модифицируем атрибут NAS-Port-Id RFC 2869 (http://tools.ietf.org/html/rfc2869) attribute name="NAS-Port-Id" type="string" code="87" */ if (request.hasAttribute(87)) { value = request.getStringAttribute(87); // атрибут имеет вид: NAS-Port-Id = vlan123 // где: 123 - VLAN ID if (value.indexOf("vlan") >= 0) { // удаляем слово vlan, дабы атрибут имел вид: NAS-Port-Id = 123 request.setStringAttribute(87, value.replaceFirst("vlan", "")); } } else { // юзеры без VLAN не нужны request.setOption("service_time", -1); } /* модифицируем атрибут ADSL-Agent-Circuit-Id RFC 4679 (http://tools.ietf.org/html/rfc4679) vendor name="ADSL-Forum" code="3561" attribute name="ADSL-Agent-Circuit-Id" code="1" type="string" */ adslAgentCircuitId = request.getVendorAttributes(3561, 1); // java.util.List<RadiusVendorAttribute> (bitel.billing.server.radius.RadiusPacket) if (adslAgentCircuitId != null) { attribute = adslAgentCircuitId.get(0); value = attribute.getStringValue().toLowerCase(); // атрибут имеет вид: ADSL-Agent-Circuit-Id = 0a:0b:0c:0d:0e:0f::255.255.255.255::1 // где: 0a:0b:0c:0d:0e:0f - МАС адрес пользователя, 255.255.255.255 - IP адрес свича, 1 - порт свича if (value.matches("^(?:[\\da-f]{2}:){5}[\\da-f]{2}::(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)::\\d{1,2}$")) { // удаляем из атрибута МАС и заменяем двойное двоеточие "::" одинарным ":" circuitId = value.substring(19).replace("::", ":"); // теперь атрибут будет имеет вид: // ADSL-Agent-Circuit-Id = 255.255.255.255:1 // где: 255.255.255.255 - IP адрес свича, 1 - порт свича attribute.setStringValue(circuitId); } }
Скрипт на событие RADIUS аутентификации:
import bitel.billing.server.contract.bean.*; import bitel.billing.server.processor.event.*; import bitel.billing.server.radius.*; import bitel.billing.server.contract.object.bean.*; import bitel.billing.common.*; import bitel.billing.server.script.bean.event.*; /** * Вбивает в лог ошибок радиус запрос сессии чтобы в мониторе было видно. * Иногда может показать ошибочный лог (очень редко, но я должен предупредить), но я не виноват что Бител нас не любит и заставляет делать такие костыли :) * * @param event RadiusAuthenticationEvent * @param con подключение к БД * @param cid ID договора * @param err код ошибки */ private void writeError(RadiusAuthenticationEvent event, java.sql.Connection con, int cid, int err) { date = event.getGenerateTime(); tableDate = TimeUtils.format(date, "yyyyMM"); logDate = TimeUtils.format(date, "yyyy-MM-dd HH:mm:ss"); mid = event.getModuleID(); request = event.getRequest(); lid = event.getLogin().getId(); login = request.getStringAttribute(1); nasId = request.getNAS().getID(); qw = "INSERT INTO log_error_" + mid + "_" + tableDate + " (dt, cid, lid, login, nas_id, error_code, log_rec_id) SELECT ?, ?, ?, ?, ?, ?, (SELECT MAX(id) FROM log_server_" + mid + "_" + tableDate + " WHERE requests LIKE '%User-Name=" + login + "\n%')"; ps = con.prepareStatement(qw); ps.setString(1, logDate); ps.setInt(2, cid); ps.setInt(3, lid); ps.setString(4, login); ps.setInt(5, nasId); ps.setInt(6, err); ps.executeUpdate(); } RadiusPacket request = event.getRequest(); // bitel.billing.server.processor.event.RadiusAuthenticationEvent RadiusPacket response = event.getResponse(); // bitel.billing.server.processor.event.RadiusAuthenticationEvent int errorCode = event.getErrorCode(); // bitel.billing.server.processor.event.RadiusAuthenticationEvent Contract contract = event.getContract(); // bitel.billing.server.processor.event.RadiusAuthenticationEvent long contractGroups = contract.getGroups(); // bitel.billing.server.contract.bean.Contract int contractId = contract.getID(); // bitel.billing.server.contract.bean.Contract // эти параметры берутся из клиента (см. п.3 инструкции по предварительной настройке) int objectConfigTypeId = 1; // тип объекта - Ethernet int objectConfigSwitchIpId = 1; // параметр объекта - IP адрес свича int objectConfigSwitchPortId = 2; // параметр объекта - Номер порта // получаем атрибут ADSL-Agent-Circuit-Id adslAgentCircuitId = request.getVendorAttributes(3561, 1); // java.util.List<RadiusVendorAttribute> (bitel.billing.server.radius.RadiusPacket) // это дефолтные значения для объекта - они будут использованы если Ваше железо не прислало эти данные String circuitIdIp = "0.0.0.0"; // IP адрес свича String circuitIdPort = "0"; // порт свича if (adslAgentCircuitId != null) { /* я не стал делать тут итерации в духе этой: for (RadiusVendorAttribute i : adslAgentCircuitId) { value = i.getStringValue().toLowerCase(); } или этой: ListIterator i = adslAgentCircuitId.listIterator(); while (i.hasNext()) { value = i.next().getStringValue(); } т.к. в списке всего 1 элемент и проще его получить через get() */ // после скрипта предобработки атрибут имеет вид: // ADSL-Agent-Circuit-Id = 255.255.255.255:1 // где: 255.255.255.255 - IP адрес свича, 1 - порт свича circuitId = adslAgentCircuitId.get(0).getStringValue(); delimIdx = circuitId.indexOf(":"); circuitIdIp = circuitId.substring(0, delimIdx); circuitIdPort = circuitId.substring(delimIdx + 1); } int switchIp = IPUtils.convertStringIPtoInt(circuitIdIp); int switchPort = Integer.parseInt(circuitIdPort); // получаем списки с IP адресами свичей и портов listValueManager = new ListValueManager(con); configSwitchIpList = listValueManager.getValues(objectConfigSwitchIpId); // java.util.List<ListValue> (bitel.billing.server.contract.object.bean.ListValueManager) configSwitchPortList = listValueManager.getValues(objectConfigSwitchPortId); // java.util.List<ListValue> (bitel.billing.server.contract.object.bean.ListValueManager) // перебираем полученные списки, чтобы узнать есть они в базе или нет, если есть - их ID-ы пригодятся в дальнейшем int configSwitchIp = 0; int configSwitchIpId = 0; int configSwitchPort = 0; int configSwitchPortId = 0; for (ListValue i : configSwitchIpList) { configSwitchIp = IPUtils.convertStringIPtoInt(i.getTitle()); if (switchIp == configSwitchIp) { configSwitchIpId = i.getId(); // int (bitel.billing.server.contract.object.bean.ListValue) break; } } for (ListValue i : configSwitchPortList) { configSwitchPort = Integer.parseInt(i.getTitle()); if (switchPort == configSwitchPort) { configSwitchPortId = i.getId(); // int (bitel.billing.server.contract.object.bean.ListValue) break; } } // ни свича ни порта в базе нет - отбиваем юзера // отбиваем, потому что массовые звонки в ТП - лучший способ узнать что кто-то нарушил процедуру установки нового свича и дать этому кому-то по башке :) if (configSwitchIpId == 0 || configSwitchPortId == 0) { error("Ahtung! No switch IP " + circuitIdIp + " or port " + circuitIdPort + " in object's catalog!"); // удаляем из ответа ненужные там атрибуты if (response.hasAttribute(6)) response.removeAttributes(6); // Service-Type if (response.hasAttribute(7)) response.removeAttributes(7); // Framed-Protocol if (response.hasAttribute(8)) response.removeAttributes(8); // Framed-IP-Address if (response.hasAttribute(85)) response.removeAttributes(85); // Acct-Interim-Interval response.setPacketType(RadiusPacket.AUTHENTICATION_REJECT); writeError(event, con, contractId, 35); // код ошибки: 35 - Истек срок жизни карточного договора return; } boolean validUser = false; // изначально юзер "не правильный" и "правильным" он станет только когда мы его проверим :) // работаем только с тему к кого все ОК, т.е. нет ошибки баланса и т.п. if (errorCode < 1) { // получаем список объектов договора objectManager = new ObjectManager(con); contractObjectList = objectManager.getObjectList(contractId); // java.util.List<ContractObject> (bitel.billing.server.contract.object.bean.ObjectManager) paramValueManager = new ParamValueManager(con); if (contractObjectList.isEmpty()) { // у договора нет объектов // добавляем объект contractObject = new ContractObject(); contractObject.setTypeId(objectConfigTypeId); contractObject.setContractId(contractId); contractObject.setDate1(TimeUtils.convertDateToCalendar(contract.getDateFrom())); contractObject.setTitle(listValueManager.getValue(configSwitchIpId).getTitle() + ":" + listValueManager.getValue(configSwitchPortId).getTitle()); objectManager.updateObject(contractObject); // обновляем значения списков contractObjectId = contractObject.getId(); contractObjectSwitchIp = new ListParamValue(); contractObjectSwitchIp.setObjectId(contractObjectId); contractObjectSwitchIp.setParamId(objectConfigSwitchIpId); contractObjectSwitchIp.setValue(configSwitchIpId); paramValueManager.updateParamValue(contractObjectSwitchIp); contractObjectSwitchPort = new ListParamValue(); contractObjectSwitchPort.setObjectId(contractObjectId); contractObjectSwitchPort.setParamId(objectConfigSwitchPortId); contractObjectSwitchPort.setValue(configSwitchPortId); paramValueManager.updateParamValue(contractObjectSwitchPort); // событие EventProcessor.getProcessor().addEvent(new ContractAddObjectEvent(contractObject)); // это хороший, годный юзер - делаем его "правильным" validUser = true; } else { // у договора есть объекты for (ContractObject i : contractObjectList) { objectId = i.getId(); objectSwitchIpListParamValue = paramValueManager.getListParamValue(objectId, objectConfigSwitchIpId).getValue(); objectSwitchIpListValue = listValueManager.getValue(objectSwitchIpListParamValue).getTitle(); objectSwitchIp = IPUtils.convertStringIPtoInt(objectSwitchIpListValue); objectSwitchPortListParamValue = paramValueManager.getListParamValue(objectId, objectConfigSwitchPortId).getValue(); objectSwitchPortListValue = listValueManager.getValue(objectSwitchPortListParamValue).getTitle(); objectSwitchPort = Integer.parseInt(objectSwitchPortListValue); if (switchIp == objectSwitchIp && switchPort == objectSwitchPort) { validUser = true; break; } else { validUser = false; } } contractLoginCount = 0; // кол-во алиасов в dialup contractObjectCount = contractObjectList.size(); // кол-во объектов договора // считаем кол-во алиасов в dialup qw = "SELECT COUNT(login) AS cnt FROM user_login_" + event.getModuleID() + " WHERE cid = ? AND date2 IS NULL"; ps = con.prepareStatement(qw); ps.setInt(1, contractId); rs = ps.executeQuery(); if (rs.first()) { contractLoginCount = rs.getInt("cnt"); } // юзер "пришел" не оттуда, откуда ждали, но т.к. кол-во рабочих логинов больше чем кол-во объектов то создаем новый объект if (!validUser && contractLoginCount > contractObjectCount) { contractObject = new ContractObject(); contractObject.setTypeId(objectConfigTypeId); contractObject.setContractId(contractId); contractObject.setDate1(TimeUtils.convertDateToCalendar(contract.getDateFrom())); contractObject.setTitle(listValueManager.getValue(configSwitchIpId).getTitle() + ":" + listValueManager.getValue(configSwitchPortId).getTitle()); objectManager.updateObject(contractObject); contractObjectId = contractObject.getId(); contractObjectSwitchIp = new ListParamValue(); contractObjectSwitchIp.setObjectId(contractObjectId); contractObjectSwitchIp.setParamId(objectConfigSwitchIpId); contractObjectSwitchIp.setValue(configSwitchIpId); paramValueManager.updateParamValue(contractObjectSwitchIp); contractObjectSwitchPort = new ListParamValue(); contractObjectSwitchPort.setObjectId(contractObjectId); contractObjectSwitchPort.setParamId(objectConfigSwitchPortId); contractObjectSwitchPort.setValue(configSwitchPortId); paramValueManager.updateParamValue(contractObjectSwitchPort); EventProcessor.getProcessor().addEvent(new ContractAddObjectEvent(contractObject)); validUser = true; } // кол-во рабочих логинов меньше чем кол-во объектов - чтобы не было путаницы удаляем все объекты и создаем новый // ситуация довольно редкая - возникает только когда клиент отказывается от доп. логина(ов) if (contractLoginCount < contractObjectCount) { for (ContractObject i : contractObjectList) { objectId = i.getId(); paramValueManager.deleteActivityParamValues(objectId); objectManager.deleteObject(objectId); EventProcessor.getProcessor().addEvent(new ContractDeleteObjectEvent(i)); } contractObject = new ContractObject(); contractObject.setTypeId(objectConfigTypeId); contractObject.setContractId(contractId); contractObject.setDate1(TimeUtils.convertDateToCalendar(contract.getDateFrom())); contractObject.setTitle(listValueManager.getValue(configSwitchIpId).getTitle() + ":" + listValueManager.getValue(configSwitchPortId).getTitle()); objectManager.updateObject(contractObject); contractObjectId = contractObject.getId(); contractObjectSwitchIp = new ListParamValue(); contractObjectSwitchIp.setObjectId(contractObjectId); contractObjectSwitchIp.setParamId(objectConfigSwitchIpId); contractObjectSwitchIp.setValue(configSwitchIpId); paramValueManager.updateParamValue(contractObjectSwitchIp); contractObjectSwitchPort = new ListParamValue(); contractObjectSwitchPort.setObjectId(contractObjectId); contractObjectSwitchPort.setParamId(objectConfigSwitchPortId); contractObjectSwitchPort.setValue(configSwitchPortId); paramValueManager.updateParamValue(contractObjectSwitchPort); EventProcessor.getProcessor().addEvent(new ContractAddObjectEvent(contractObject)); validUser = true; } } } // этот юзер по прежнему "не правильный" (см. начало) и поэтому отбиваем его if (!validUser) { // удаляем из ответа ненужные там атрибуты if (response.hasAttribute(6)) response.removeAttributes(6); // Service-Type if (response.hasAttribute(7)) response.removeAttributes(7); // Framed-Protocol if (response.hasAttribute(8)) response.removeAttributes(8); // Framed-IP-Address if (response.hasAttribute(85)) response.removeAttributes(85); // Acct-Interim-Interval response.setPacketType(RadiusPacket.AUTHENTICATION_REJECT); writeError(event, con, contractId, 22); // код ошибки: 22 - Запрещён вход на данный телефон return; }
Чтобы в мониторе соединений ошибки, которые выставляются скриптом выше было проще распознать - необходимо добавить в конфигурацию модуля dialup строки:
error.message.code.22=Подключение не по адресу регистрирации error.message.code.35=Свича нет в базе объектов
Настройка оборудования
Настройка PPPoE Circuit Id Insertion в свичах D-Link
config pppoe circuit_id_insertion ports <клиентские порты> state enable
config pppoe circuit_id_insertion ports <up/downlink порты> state disable
config pppoe circuit_id_insertion state enable
Работоспособность проверена на DES-1228/ME, DES-3028 и DES-3526)
Обращаю внимание, что в DES-3526 при включении PPPoE Circuit Id Insertion создается PCF ACL:
#sh acce p 1 a 1 Command: show access_profile profile_id 1 access_id 1 Access Profile Table Access Profile ID : 1 Type : Packet Content ================================================================================ Owner : PPPoE_Circuit_ID_Insertion Masks : Offset 16-31 : 0xffff0000 00000000 00000000 00000000 Access ID: 1 Mode: Owner : PPPoE_Circuit_ID_Insertion Port : 1 ---------------------------------------------------- Offset 16-31 : 0x88630000 00000000 00000000 00000000 ================================================================================
Если Вы используете ACL, то у Вас этот access_profile добавится в конец списка и если у Вас где-то выше по списку есть правило, которое разрешает трафик 0x8863, то в ADSL-Agent-Circuit-Id Вы можете ничего не получить. Если это произойдет - перепишите ACL с учетом удаления правила разрешающего трафик 0x8863, и добавлением его путем включения PPPoE Circuit Id Insertion, т.е. если у Вас было так:
create access_profile ethernet ethernet_type profile_id 1 config access_profile profile_id 1 add access_id auto_assign ethernet ethernet_type 0x8863 port 1-24 permit config access_profile profile_id 1 add access_id auto_assign ethernet ethernet_type 0x8864 port 1-24 permit
то сделайте так:
config pppoe circuit_id_insertion ports 1-24 state enable config pppoe circuit_id_insertion state enable create access_profile ethernet ethernet_type profile_id 2 config access_profile profile_id 2 add access_id auto_assign ethernet ethernet_type 0x8864 port 1-24 permit
1-й шаг как раз создаст profile_id 1 разрешающий трафик 0x8863