Pull to refresh
74.42
Холдинг Т1
Многопрофильный ИТ-холдинг

Что делать, если не знаешь, как работает ПО

Reading time7 min
Views10K

Источник

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

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

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

Специальный продукт для анализа трафика 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. Возможность контролировать «черные ящики». Таких приложений много и не всегда понятно как за ними следить. Пример ― наша ситуация с АБС.

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

Автор статьи Антон Касимов, архитектор систем мониторинга, компания «Техносерв».
Only registered users can participate in poll. Log in, please.
Пользуетесь ли вы разбором трафика для диагностики проблем в приложениях?
68.85% Да, пользуюсь Wireshark или чем-то бесплатным42
1.64% Да, пользуюсь коммерческой системой мониторинга трафика1
29.51% Нет, мне с моим приложением и так все понятно18
61 users voted. 15 users abstained.
Tags:
Hubs:
+12
Comments2

Articles

Information

Website
t1.ru
Registered
Founded
Employees
over 10,000 employees
Location
Россия
Representative
Холдинг Т1