Источник

Мы тоже не любим софт, который неизвестно как работает. Если программа ? черный ящик, при каждой непонятной ситуации остается ровно два варианта: попробовать приложить подорожник или обратиться к производителю. Но, во-первых, мы не знаем, где растет столько подорожника, а, во-вторых, обращаться по разным мелочам к вендору тоже как-то не ацаца.

Несколько лет назад, у одного из наших заказчиков нашелся ровно такой софт: на входе запросы, на выходе ответы, а внутри непонятно что. Называется автоматизированная банковская система или АБС. Но по сути это база данных, которая в общем случае обрабатывает запросы из процессинга и возвращает результат. В результате содержится ответ: может банк провести эту операцию или нет. Подступиться к этой базе данных с внешним запросом нет никакой возможности в силу ее архитектурных ограничений. В одном из недавних проектов снова столкнулись с таким же ПО в ритейле и подумали, что пора бы уже поделиться своими наработками.

В прошлой статье мы рассказывали, как прослушивание трафика помогло одному из клиентов Техносерва отловить внутренние проблемы софта. В этой будет технический разбор той давней задачи (руки-то помнят), а в конце — два важных бенифита такого вида мониторинга.

Специальный продукт для анализа трафика 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.
На выходе заказчик получил график с количеством транзакций. На скриншоте ниже отображен недельный интервал. Теперь любое аномальное поведение этого графика трактуется системой как сбой. Мы настроили корреляцию событий с другими приложениями бизнес-процесса, и администраторы могут сразу увидеть возможную причину отклонения от нормы. Разумеется, в разные дни поведение графика может отличаться. Например, в субботу и воскресенье нет проседаний в дневное время по количеству транзакций ? это норма для выходных дней и событие не генерируется. Вот такой искусственный интеллект.



За время работы с системами мониторинга пользовательского трафика мы выявили два важных преимущества (их может и больше, но пока остановились на двух):

  1. Не создается дополнительная нагрузка на бизнес-приложение. Нет нужды встраивать в приложение специальные агенты, просить разрешения у админов чего-то там перезагрузить и вообще беспокоить людей по пустякам.
  2. Возможность контролировать «черные ящики». Таких приложений много и не всегда понятно как за ними следить. Пример ? наша ситуация с АБС.

Приглашаем пройти опрос, поделиться в комментариях примерами приложений-черных ящиков и рассказать, как вы их контролируете.

Автор статьи Антон Касимов, архитектор систем мониторинга, компания «Техносерв».

Комментарии (0)