
Источник
Мы тоже не любим софт, который неизвестно как работает. Если программа ― черный ящик, при каждой непонятной ситуации остается ровно два варианта: попробовать приложить подорожник или обратиться к производителю. Но, во-первых, мы не знаем, где растет столько подорожника, а, во-вторых, обращаться по разным мелочам к вендору тоже как-то не ацаца.
Несколько лет назад, у одного из наших заказчиков нашелся ровно такой софт: на входе запросы, на выходе ответы, а внутри непонятно что. Называется автоматизированная банковская система или АБС. Но по сути это база данных, которая в общем случае обрабатывает запросы из процессинга и возвращает результат. В результате содержится ответ: может банк провести эту операцию или нет. Подступиться к этой базе данных с внешним запросом нет никакой возможности в силу ее архитектурных ограничений. В одном из недавних проектов снова столкнулись с таким же ПО в ритейле и подумали, что пора бы уже поделиться своими наработками.
В прошлой статье мы рассказывали, как прослушивание трафика помогло одному из клиентов Техносерва отловить внутренние проблемы софта. В этой будет технический разбор той давней задачи (руки-то помнят), а в конце — два важных бенифита такого вида мониторинга.
Специальный продукт для анализа трафика MicroFocus RUM (подробнее о нем в предыдущей статье по ссылке выше), конечно, поддерживает разнообразные сетевые протоколы, но вот именно те, что нам нужны ― нет. А нужно было прослушать служебные RPC-запросы от внутрибанковских систем к АБС и трафик по протоколу ISO8583 от внешних устройств (банкоматов и POS-терминалов) всё к той же АБС.
Анализируем данные по протоколу RPC
Постановка задачи от заказчика выглядела примерно так:
«Парни, вот вам трафик за несколько часов для анализа. Тут у меня от разных банковских систем к АБС бегают транзакции в виде запросов-ответов. Нужно знать время выполнения каждого такого запроса, получать значения некоторых полей и считать количество ошибок. На выходе нужен отчет в табличном виде».
Самым сложным в этой задаче был анализ полученного трафика. Поиск последовательностей и разделение потока на отдельные транзакции потребовало кропотливого труда. После анализа выяснилось, что каждая транзакция это XML-пакет с одинаковым идентификатором для запроса и ответа, а также набором других полей. На скриншоте пример этого трафика в интерфейсе Wireshark и соответствующий ему XML.

Чтобы RUM научился понимать различные типы сетевых данных, нужно эти данные ему описать. Для этого мы разработали скрипт на C++. Ниже код для разбора RPC-трафика.
Код разбора трафика
#include "./ProtocolRPCRQ.hpp" #include <stdlib.h> #include <stdio.h> #include <signal.h> #include <setjmp.h> #include <iostream> #include <fstream> #include <base/ProtocolConfig.hpp> #include <base/ProcessingState.hpp> #include <utils/LogHelper.hpp> using namespace rum::public_sdk; sigjmp_buf mark; void segfault_sigaction(int signal) { ; } RPCRQProcessor::RPCRQProcessor( const char* name, IProtocolProcessor* next ) : ProtocolProcessorBase( name, next, "protocols.RPCRQProcessor" ) { } void RPCRQProcessor::initialize( const ProtocolConfig& config ) { ; } /*override*/ void RPCRQProcessor::process( ProtocolEvent& event, ProcessingState& ps, ProcessingMode mode ) { bool isParsed; try { if (mode != PROCESSING_FULL ) { forwardToNextProcessor( event, ps, mode); return; } EventParsingContext eventContext( event ); isParsed = doAllParsingWork( ps.getRequest(), ps.getResponse(), eventContext); if (isParsed) { forwardToNextProcessor(event, ps, mode); } else return; } catch (...) { LOG4PROBE_ERROR( getDefaultLogger(), "logger1"); } } String RPCRQProcessor::search_substring(String data, String start, String stop) { String empty = ""; try { size_t start_pos = data.find(start); if (start_pos!=std::string::npos) data = data.substr(start_pos+start.length()); size_t stop_pos = data.find(stop); if (stop_pos!=std::string::npos) data = data.substr(0,stop_pos); if (data.find("xmlns")!=std::string::npos) return empty; if ((start_pos==std::string::npos) || (stop_pos==std::string::npos)) return empty; else return data; } catch (...) {return empty;} } String RPCRQProcessor::stream_to_string(DataStream& stream,int offset) { String rumstring = ""; String empty = ""; int i = 0; try { while ( (!stream.eof()) && (i<10000)) { i++; if (stream.peek()!=0xff) { unsigned char value = stream.get(); if ((value!=0) && (value!=0xff)) rumstring += value; } else { stream.skip(1); } } } catch (...) {return empty;} if (rumstring.length()>offset) return rumstring.substr(offset); else return empty; } bool RPCRQProcessor::doAllParsingWork( DataStream& request, DataStream& response, EventParsingContext &context ) { try { rum::public_sdk::ActionInfo iactionInfo = context.getActionInfo(); String schema = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"; String strdata = stream_to_string(request,45); String message = search_substring(strdata,"<message type=\"RPC_","</message>"); if (message!="") { String msgType = search_substring(strdata,"<message type=\"","\">"); String origMsgID = search_substring(message,"<origMsgID>","</origMsgID>"); if (origMsgID=="") origMsgID = search_substring(message,"<origMsgID"+schema,"</origMsgID>"); String origSysID = search_substring(message,"<origSysID>","</origSysID>"); if (origSysID=="") origSysID = search_substring(message,"<origSysID"+schema,"</origSysID>"); String targSysID = search_substring(message,"<targSysID>","</targSysID>"); if (targSysID=="") targSysID = search_substring(message,"<targSysID"+schema,"</targSysID>"); String origPrcID = search_substring(message,"<origPrcID>","</origPrcID>"); if (origPrcID=="") origPrcID = search_substring(message,"<origPrcID"+schema,"</origPrcID>"); String timeStamp = search_substring(message,"<timeStamp>","</timeStamp>"); if (timeStamp=="") timeStamp = search_substring(message,"<timeStamp"+schema,"</timeStamp>"); String CmdName = search_substring(message,"<command name=\"","\">"); if (CmdName=="") CmdName = "PARSING_ERROR"; iactionInfo.addKeyValue("msgType" , msgType , false ); iactionInfo.addKeyValue("origMsgID", origMsgID, false ); iactionInfo.addKeyValue("origSysID", origSysID, false ); iactionInfo.addKeyValue("targSysID", targSysID, false ); iactionInfo.addKeyValue("origPrcID", origPrcID, false ); iactionInfo.addKeyValue("timeStamp", timeStamp, false ); iactionInfo.addKeyValue("CmdName" , CmdName, false ); iactionInfo.addKeyValue("x-action-descriptor" , CmdName, false ); iactionInfo.setDescriptor(CmdName); String respstrdata = stream_to_string(response,45); String resmessage = search_substring(respstrdata,"<message type=\"RPC_","</message>"); if (resmessage!="") { String retCode = search_substring(resmessage,"<retCode>","</retCode>"); if (retCode!="") { iactionInfo.addKeyValue("retCode" , retCode, false ); if (retCode.find("OK") != std::string::npos) { context.getEvent().setStatusCode(0); } else context.getEvent().setStatusCode(1); } else { iactionInfo.addKeyValue("retCode" , "NOTFOUND", false ); context.getEvent().setStatusCode(2); } } } if (message=="") return false; else return true; } catch (...) { LOG4PROBE_ERROR( getDefaultLogger(), "exception in RPCRQProcessor" ); context.getEvent().setStatusCode(3); } } ////////////////////////////////////////////////////////////// // class MyProtocolParser::EventParsingContext ////////////////////////////////////////////////////////////// RPCRQProcessor::EventParsingContext::EventParsingContext( ProtocolEvent& event ) : _event(event) { try { _connectionContext = _event.getSessionContext(); if ( NULL == _connectionContext.get() ) { _connectionContext = new ConnectionInfo(); _event.setSessionContext( _connectionContext.get() ); } } catch (...) { std::ofstream fileSTRINGTEST; fileSTRINGTEST.open("/home/rum/fileSTRINGTEST_error2.out",std::ios::out|std::ios::app|std::ios::binary); fileSTRINGTEST << "--------------------------------\n"; fileSTRINGTEST.close(); } } // Declaring Factory for creating processor RUM_PROBE_EXPORT_PROCESSOR( RPCRQProcessor, getRPCRQProcessor);
На выходе получился такой элегантный отчет:

Заказчик доволен, а мы получили новые навыки в продукте.
Анализируем данные по протоколу ISO8583
Задача аналогична предыдущей с небольшими изменениями:
«Парни, вот вам новая порция трафика. У меня есть агрегатор, который собирает транзакции с моих банкоматов и POS-терминалов. Все транзакции летят в АБС. Хочу знать — сколько у меня было таких транзакций, их статус и время выполнения каждого запроса. На выходе нужен отчет в виде графика».
Как и в предыдущей задаче, пакеты сетевого трафика представляли из себя XML. Только со значительно большим количеством полей. С помощью нового кода мы отсеяли все ненужное и получили примерно такой же XML для анализа и подсчета транзакций как в случае с RPC.
На выходе заказчик получил график с количеством транзакций. На скриншоте ниже отображен недельный интервал. Теперь любое аномальное поведение этого графика трактуется системой как сбой. Мы настроили корреляцию событий с другими приложениями бизнес-процесса, и администраторы могут сразу увидеть возможную причину отклонения от нормы. Разумеется, в разные дни поведение графика может отличаться. Например, в субботу и воскресенье нет проседаний в дневное время по количеству транзакций ― это норма для выходных дней и событие не генерируется. Вот такой искусственный интеллект.

За время работы с системами мониторинга пользовательского трафика мы выявили два важных преимущества (их может и больше, но пока остановились на двух):
- Не создается дополнительная нагрузка на бизнес-приложение. Нет нужды встраивать в приложение специальные агенты, просить разрешения у админов чего-то там перезагрузить и вообще беспокоить людей по пустякам.
- Возможность контролировать «черные ящики». Таких приложений много и не всегда понятно как за ними следить. Пример ― наша ситуация с АБС.
Приглашаем пройти опрос, поделиться в комментариях примерами приложений-черных ящиков и рассказать, как вы их контролируете.
Автор статьи Антон Касимов, архитектор систем мониторинга, компания «Техносерв».
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Пользуетесь ли вы разбором трафика для диагностики проблем в приложениях?
68.85%Да, пользуюсь Wireshark или чем-то бесплатным42
1.64%Да, пользуюсь коммерческой системой мониторинга трафика1
29.51%Нет, мне с моим приложением и так все понятно18
Проголосовал 61 пользователь. Воздержались 15 пользователей.

