Ограничение доступа на основе объектов

Материал из BiTel WiKi

Перейти к: навигация, поиск

ВНИМАНИЕ! Cтатья еще не закончена!

Ограничение доступа пользователей на основе объектов, использующих информацию из ADSL-Agent-Circuit-Id (RFC 4679).

Использование данной технологии позволяет так сказать "привязать" пользователя к порту свича и/или DSLAM без использования ограничений по МАС адресам и т.п.
Ниже рассмотрен случай с "привязкой" к IP адресу свича и номеру порта с которого осужествляется выход в сеть.


Как это выглядит в договоре:

  • Объект абонента, для которого получен Circuit Id.
    Circuit Id есть

  • Объект абонента, для которого Circuit Id не получен.
    Circuit Id нет


Содержание

Предварительная настройка.

  1. Создаем тип объекта.
    Значение имеет только макрос имени, должен быть: ${list:1}:${list:2}. Название значения не имеет (в данном случае: Ethernet):
    Тип объекта

  2. Создаем параметры объекта "IP адрес свича" (или, например, "IP адрес DSLAM") и "Номер порта".:
    "Тип параметра" обязательно должен быть "список", т.к. в коде обрабатываются именно списки, а не текст, дата или адрес!
    Параметры объекта

  3. Привязываем параметры, созданные в п.2 к типу объекта из п.1:
    Параметры типов

  4. Заполняем списки параметров:
    • IP адрес свича:
      Обязательно впишите IP адрес 0.0.0.0, т.к. этот IP адрес будет использован в случае если в атрибуте ADSL-Agent-Circuit-Id не будет IP адреса.
      В списке должны быть перечислены IP адреса всех свичей, с которых Вы планируете получать информацию о Circuit-Id абонента.
      Значения списков: IP адрес свича

    • Номер порта:
      Обязательно впишите номер порта 00, т.к. это номер порта будет использован в случае если в атрибуте ADSL-Agent-Circuit-Id не будет номера порта.
      В списке должны быть перечислены все порты, с которых Вы планируете получать информацию о Circuit-Id абонента,:
      т.е. если у вас на доступе стоят свичи в которых 24 порта, то Вы перечисляете их все с 1-го по 24-й, как это показано на картинке.
      Значения списков: Номер порта

Словарь 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

Личные инструменты