Все начиналось, с некорректно работающее оборудки которая не понимала SNMP запросы, и работать надо было через telnet команды, ну так начнем, для работы с telnet я использовал сторонии класс , честно говор не помню где его скачал, думаю наити что то похожее не проблема
require_once(__DIR__ . '/TelnetClient/TelnetClient.php');
Добавил наши настройки в самый верх файла это будет очень удобно, укажем такие параметры как логин пароль хост к базе данных биллинга гидры (Oracle DB).
Функция Start , будет выполнять запуск второстепенных функции , которые выполнят наш функционал по включения\отключению абонентов в зависимости от положительного баланса или отрицательного
function Start($conf)
{
$filelog = new FileLog($conf);
$mac_on_really = [];
$mac_off_really = [];
$hydra = new HydraAPI($conf);
$mac_on = $hydra->Get_BDCOM_ONOFF(true);
$mac_off = $hydra->Get_BDCOM_ONOFF(false);
$lines = file($this->conf['bdcom_onoff_file'], FILE_IGNORE_NEW_LINES);
foreach ($mac_on as $mac) {
foreach ($lines as $line) {
$finded = false;
// Ищем наш мак с строке результата
if(strpos($line, $mac)>-1)
{
if(trim($line) === trim("$mac:enable")) { $finded = true; break; }
}
}
if($finded === false) { $mac_on_really[] = $mac; }
}
foreach ($mac_off as $mac) {
foreach ($lines as $line) {
$finded = false;
// Ищем наш мак с строке результата
if(strpos($line, $mac)>-1)
{
if(trim($line) === trim("$mac:disable")) { $finded = true; break; }
}
}
if($finded === false) { $mac_off_really[] = $mac; }
}
TelnetOnOFFBDCOM($conf,$mac_on_really, true);
TelnetOnOFFBDCOM($conf,$mac_off_really, false);
}
Функция которая будет включать\выключать устройства BDCOM , создадим объекты классов такие как Filelog - работа с логами, TelnetClient - работа с telnet, используя команды и список мак адресов которые нужно включить\отключить выполним нужные команды запишем результат в файл логов
// Функция которая будет вкл/выкл бдком и записывать значения в фаил
function TelnetOnOFFBDCOM($conf,$mac_arr,$onoff = true)
{
$epon_port = false;
$result_onoff = false;
$filelog = new FileLog($conf);
$telnet = new TelnetClient($host, $port);
$telnet->connect();
// Логин
$telnet->setPrompt('/(ogin|name|word|UserName):.*$/');
$telnet->sendCommand('netadmin', true);
// пароль
$telnet->setPrompt('/(word|PassWord):.*$/');
$telnet->sendCommand('netadmin', true);
$telnet->setPrompt('>');
$telnet->sendCommand('enable', true);
$telnet->setPrompt('#');
$telnet->sendCommand('config', true);
$telnet->setPrompt('#');
if($onoff) $command = "enable" else $command = "disable";
foreach ($mac_arr as $mac_item) {
// конвертируем мак в нужный формат
$mac = ConvertMacToDelimitier($mac_item,'.');
$telnet->sendCommand('show epon onu-information mac-address '.$mac, true);
$telnet->setPrompt('registered');
// получаем ответ ввиде массива для каждои строки
$array = $telnet->exec('', true);
foreach($array as $item)
{ // если в строке есть искомый мак
if(strpos($item,$mac)>-1)
{
// если есть второе соотвествие
if(strpos($item,'EPON')>-1)
{
$epon_port_arr = explode(':',$item);
// получаем EPON порт
$epon_port = trim($epon_port_arr[0]).':'.trim(substr($epon_port_arr[1], 0,3));
break;
}
}
} // конец перебора ответа
$telnet->sendCommand("", true);
if($epon_port)
{
$port = str_replace("EPON",'',$epon_port);
$telnet->sendCommand("interface epON ".$port);
$telnet->setPrompt('#');
$telnet->sendCommand("epon onu catv $command", true);
$telnet->setPrompt('/');
$result_onoff = true;
}
// если результат успешно то запишем результат в фаил
if($result_onoff) {
$str = ;
// ищем результат в фаиле
$data = $filelog->GetStrFromFile($mac);
if($data)
{
$result = $data['data'];
$line = $data['line'];
// делаем замену строки
$filelog->SetReplaceStr($line, $mac.':'.$command);
}
else
{
// добавляем новую строку
$filelog->SetRewriteFile($mac.':'.$command);
}
$log_str = date("Y-m-d h:i:s")." mac ".$mac.':'.$command;
$filelog->SetRewriteLogFile($log_str);
}
} // перебор нескольких мак адресов
$telnet->sendCommand('exit', true);
$telnet->disconnect();
}
Напишем класс который будет работать с логами.
class FileLog
{
public $conf;
function __construct($conf)
{
$this->conf = $conf;
}
function SetRewriteLogFile($str)
{
$fhandle = fopen($this->conf['log_file_name'],"w");
fwrite($fhandle,$content);
fclose($fhandle);
}
function SetRewriteFile($str)
{
$fhandle = fopen($this->conf['bdcom_onoff_file'],"w");
fwrite($fhandle,$content);
fclose($fhandle);
}
function SetAddStringFile($str)
{
$fp = fopen($this->conf['log_file_name'], 'a+'); //- а+ добавляет новые данные после существующих
fwrite($fp, $str."\r\n"); //а "r\n\" - вставляет их каждый рас с новой строки
fclose($fp);
}
// Прочитать строку в которои есть этот мак
function GetStrFromFile($mac)
{
$result = false;
$line = 0;
$fp = @fopen($this->conf['bdcom_onoff_file'], "r");
if ($fp) {
while (($buffer = fgets($fp, 4096)) !== false) {
if(strpos($buffer, $mac) !== false) { $result['data'] = explode(':', $buffer); $result['line'] = $line; break; }
$line = $line +1;
}
if (!feof($fp)) {
echo "Ошибка: fgets() неожиданно потерпел неудачу\n";
}
fclose($fp);
}
return $result;
}
Напишем класс который будет работать с биллингом Hydra (Oracle DB), наверное единственная функция в классе которая будет иметь интерес Get_BDCOM_ONOFF - позволяет используя SQL запрос получить нам должников или абонентов с положительным балансом.
class HydraAPI
{
public $conn;
function __construct($conf)
{
try {
$conn = @oci_connect($conf['hydra_user'], $conf['hydra_pwd'], $conf['hydra_host'] . '/' . $conf['hydra_db'], 'AL32UTF8');
if (!$conn) {
$e = oci_error();
// trigger_error(htmlentities($e['message'], ENT_QUOTES), E_USER_ERROR);
}
$this->conn = $conn;
}catch(Exception $e) {}
}
public function Get_BDCOM_ONOFF($onoff = true)
{
try{
if($onoff) { $balance_str = '>'; } else { $balance_str = '<'; }
$query = "SELECT DISTINCT
CONCAT('https://95.182.107.243:8000/subjects/persons/edit/',OS.N_SUBJECT_ID) URL,
SU.VC_BASE_SUBJECT_NAME,
OV.VC_VALUE, -- Значение
g.vc_name,
SU.N_SUBJ_STATE_ID
FROM SI_V_OBJ_SUBJECTS OS,
si_subj_accounts sa,
SI_V_USERS SU,
SR_GOODS G,
SI_OBJ_OBJECTS SOO,
SI_V_OBJECTS_SIMPLE O
LEFT JOIN SI_V_OBJ_VALUES OV on (OV.N_GOOD_VALUE_TYPE_ID in (55898801,56112401) AND O.N_OBJECT_ID = OV.N_OBJECT_ID )
WHERE OS.N_OBJECT_ID = O.N_OBJECT_ID
AND g.n_good_id = o.n_good_id
AND SU.N_CUSTOMER_ID = OS.N_SUBJECT_ID
AND SU.N_SUBJ_STATE_ID = 2011
AND sa.n_subject_id= os.n_subject_id
AND SI_USERS_PKG_S.GET_ACCOUNT_BALANCE_SUM(sa.n_account_id) $balance_str 0
AND OV.VC_VALUE IS NOT NULL
and exists (Select * from si_v_subscriptions ss where ss.n_customer_id = su.n_customer_id and ss.n_service_id in (54614801) and ss.c_fl_closed='N' )
AND soo.n_object_id = o.n_object_id
AND soo.n_bind_object_id in (SELECT obj2.n_object_id FROM SI_V_OBJECTS OBJ2 WHERE obj2.n_good_id=56105201 and obj2.n_object_id = 56107601)";
$stid = oci_parse($this->conn, $query);
oci_execute($stid);
while ($row = oci_fetch_array($stid, OCI_ASSOC + OCI_RETURN_NULLS)) {
$MAC = $row['VC_VALUE'];
$result[] = $MAC;
}
return $result;
}catch (\Exception $e){ return false;}
}
}
Комментарии (9)
joffer
31.08.2022 19:58+1ощущение, что это перевод какой-то записи со StackOverFlow)
текст бы вычитать, на ошибки проверить, придать какой-то структурности и объяснить, что делаем, зачем и почему так или нет - после этого скорее всего станет ясно, что для такой аудитории как "Хабр" статья не то чтобы... "слабая"... охохо, в общем, этому материалу в публикацию было сильно рано
п.с: так вот из-за каких статей до сих пор пишут "пхп мёртв")
GreekIgor Автор
01.09.2022 06:56Неа , это не перевод статьи, статья не является каким то ярким примером для подражания, это просто статья начинающего хабаровчанина, так что попрошу сильно ногами не пинать, по возможности и дальнейшему росту буду уже выкладывать более интересные и познавательные статьи
martin74ua
Убожество...
Управление доступом абонента - это не задача сети доступа, это задача браса.
Далее, зачем постоянно искать абонентов? Вы что, не знаете куда он подключен????
GreekIgor Автор
Можно было конечно это решить и через брас, но к сожалению я не имею к нему доступа, а в чем заключается поиск абонентов - это вы имеете ввиду поиск по мак адресу порта абонента или же перебор абонентов на отключение\включение - если этот вопрос касается абонентов которых перебирает скрипт - то более простого решения я не нашел
martin74ua
Если стоит задача отключить пользователя - то мы знаем, что это за пользователь. И знаем, что он включен в epon0/1:34. Зачем его еще как то искать???
Дальше. Ну хорошо. Выключили вы его. А как теперь бедному отключенному пользователю попасть в личный кабинет и узнать, что он вам денег то должен? Или пусть звонит?
Не имеете доступа к брасу - пусть отключением занимаются те, кто имеет... Им достаточно один COA послать и все произойдет....
4lex
Не всегда, sub-интерфейс onu-шки может измениться при перезагрузке BDCom (если не записать конфиг когда она забиндилась) или же намеренно перебиндить ее...
martin74ua
Еще и телнетом на голову ходить... Ей от этого больно. Минимизируйте работу с головой по телнету, лучше по снмп прочитать все, что надо...
arpnet
Не соглашусь от части с Вами, у каждого сеть настроена по своему, и бывает необходимо глушить абонента на уровне сети, т.к. до самого браса может быть настроен доступ например к коллекции медиатики, по хорошему считаю глушить и на брасе и на порту, но я бы подумал в сторону acl чтобы был доступ например к биллингу и сайту компании.
GreekIgor Автор
Я не особо разбираюсь в acl в брассах и тп. , но было время когда я связывал старый биллинг с радиусом и писал команды в брас которые обрывали соединение, но потом купили новый биллинг и тут уже доступ был ограничен на нет, и данный пример можно сказать просто костыль в систему в которую уже нельзя как то модифицировать или дополнять