Russian Qt Forum

Qt => Работа с сетью => Тема начата: m_ax от Январь 30, 2024, 14:45



Название: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: m_ax от Январь 30, 2024, 14:45
Доброго времени суток, коллеги!)

На собеседовании дали тестовое задание разработать Tcp proxy server для Postgresql с возможностью логирования
всех SQL запросов, проходящих через него.
При этом нужно использовать Berkley sockets (select/poll/epoll). Прочих зависимостей быть не должно.
Прокси должен уметь обрабатывать большое количество соединений без создания потока
(thread) на каждое соединение. Необходимо распарсить сетевые пакеты, проходящие через
прокси, в которых содержатся SQL запросы, извлечь эти запросы из пакетов и записать их
в файл в виде текста (по одному запросу в строке, структура неважна). И т.д..

Тема для меня совсем новая, раньше сетевым программированием не занимался.
Поэтому пришлось убить все выходные на расскуривание данного вопроса.

Своял код (проект прилагаю). При реализации из select/poll/epoll   выбор пал на poll.
Если кому не сложно и кто в теме, покритикуйте пожалуйста.
Может как то по элегантнее можно реализовать. Боюсь, у меня уже взгляд замыленный..  :)

Заранее спасибо)




Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: ssoft от Январь 30, 2024, 17:04
С сетью работал оочень давно). Похоже, что должно работать.

Цитировать
Необходимо распарсить сетевые пакеты, проходящие через
прокси, в которых содержатся SQL запросы, извлечь эти запросы из пакетов и записать их
в файл в виде текста (по одному запросу в строке, структура неважна)

Вот этой части не нашел. В обработчике только вывод в cout.

По идее SQL запросы необходимо собирать из сетевых пакетов.
При этом пакеты могут неожиданно разрываться и слипаться между собой.
Должен быть какой-то признак окончания запроса (м.б. символ";").


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: m_ax от Январь 30, 2024, 17:39
Цитировать
Вот этой части не нашел. В обработчике только вывод в cout.
Да, спасибо за замечание) Парсинг запросов я пока не делал. Этим будет заниматься
Код
C++ (Qt)
data_handler
 
в методе run:
Код
C++ (Qt)
void run(std::ostream & logger, const received_data_handler_t & data_handler);
 
В ТЗ сказано:
Цитировать
Необходимо распарсить сетевые пакеты, проходящие через
прокси, в которых содержатся SQL запросы, извлечь эти запросы из пакетов и записать их
в файл в виде текста (по одному запросу в строке, структура неважна).

По идее SQL запросы необходимо собирать из сетевых пакетов.
При этом пакеты могут неожиданно разрываться и слипаться между собой.
Должен быть какой-то признак окончания запроса (м.б. символ";").
Вот. Займусь как раз этим вопросом) Эта тема для меня тоже пока новая  :)



Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: Old от Январь 30, 2024, 18:47
Так а проксик то работает? :)


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: m_ax от Январь 30, 2024, 19:11
Так а проксик то работает? :)
Ну я у себя запускал, работает) Но на большом числе клиентов не тестировал)


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: qate от Январь 30, 2024, 23:15
а почему m_remote_sd один ? если я правильно понял задачу - будет, например 10 клиентов к прокси, которая сделает 10 коннектов к БД


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: m_ax от Январь 30, 2024, 23:46
а почему m_remote_sd один ? если я правильно понял задачу - будет, например 10 клиентов к прокси, которая сделает 10 коннектов к БД

А одного m_remote_sd для этого достаточно. Прокси последовательно перебирает всех активных клиентов (событие POLLIN), читает данные от каждого, обрабатывает и пробрасывает  их на remote_server (через сокет m_remote_sd)


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: qate от Январь 31, 2024, 00:53
а как сервер через прокси ответит нужному клиенту (о выполнении запроса) если он не знает кому ?


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: m_ax от Январь 31, 2024, 08:17
а как сервер через прокси ответит нужному клиенту (о выполнении запроса) если он не знает кому ?

Ну по логике, сервер и не должен ничего знать о том, какой клиент отправил запрос. Его задача получить запрос, обработать и отправить ответ.
Всю консолидацию данных между сервером и клиентами берёт на себя прокси.
Сейчас сделано (возможно топорно) так:
У прокси есть список активных клиентов. Он последовательно перебирает их, читает у каждого запрос, обрабатывает его и шлёт дальше на удалённый сервер. Дожидается ответа и шлёт ответ клиенту. Затем переходит к следующему в списке клиенту и т.д..
Здесь (в моей реализации), я подозреваю, есть слабое место: прокси должен дождаться ответа от сервера, прежде чем перекинуть серверу следующего клиента..
Здесь меня это смущает, конечно :)    


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: Old от Январь 31, 2024, 08:49
Как я понимаю, сейчас прокси может обрабатывать один запрос от одного клиента. Только после завершения обмена с одним клиентом, происходит переход к следующему.
Но это не очень эффективно, если мы отправили серверу запрос, который он будет обрабатывать 5 секунд, а от других клиентов есть запросы, которые сервер сможет выполнить параллельно за 1 сек, то из-за прокси клиенты будут ждать, пока выполниться долгий запрос.
Т.е. все запросы выполняются последовательно, а тот же postgres может их обрабатывать параллельно.

Логика должна быть другой, на старте поднимается только сокет, который принимает подключения от клиентов.
При подключении нового клиента, выполняем подключение к серверу и после этого имеем пару сокетов client-proxy и proxy-server, вот все что приходит по первому сокету, нужно отправить во второй и наоборот. Если один из сокетов закрывается, то нужно закрыть и второй.
Как правило эти два сокета храняться в объектах, которые называют session. :)


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: m_ax от Январь 31, 2024, 09:14
Логика должна быть другой, на старте поднимается только сокет, который принимает подключения от клиентов.
При подключении нового клиента, выполняем подключение к серверу и после этого имеем пару сокетов client-proxy и proxy-server, вот все что приходит по первому сокету, нужно отправить во второй и наоборот. Если один из сокетов закрывается, то нужно закрыть и второй.
Как правило эти два сокета храняться в объектах, которые называют session. :)
Согласен) 
По сути, m_remote_sd должен быть слушающим. Запускать poll и ждать событий POLLOUT от удалённого сервера..
Переделаю сегодня. Спасибо)


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: qate от Январь 31, 2024, 09:34
Дожидается ответа и шлёт ответ клиенту

теперь ясно )

а как решается проблема, что трафик между клиентом, например psql, и сервером - это не plain text и вычленить sql запросы не очевидно как ?


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: m_ax от Январь 31, 2024, 09:51
Дожидается ответа и шлёт ответ клиенту

теперь ясно )

а как решается проблема, что трафик между клиентом, например psql, и сервером - это не plain text и вычленить sql запросы не очевидно как ?

Я пока предполагаю, что это просто plain text в котором могут быть sql запросы.
Сейчас реализовано так:
Код
C++ (Qt)
proxy.run(std::cout, [&](const std::string & data)
       {
           auto query_list = sandbox::get_sql_query(data);
           for (const auto & query : query_list)
               out << query << std::endl;
       });
 
где парсер просто извлекает из текста запросы:
Код
C++ (Qt)
#ifndef SQL_PARSER_H
#define SQL_PARSER_H
 
#include <regex>
#include <string>
#include <list>
 
namespace sandbox {
 
inline std::list<std::string> get_sql_query(const std::string & src)
{
   std::regex query_regex("(SELECT[^;]+;)");
 
   auto query_begin = std::sregex_iterator(src.begin(), src.end(), query_regex);
   auto query_end = std::sregex_iterator();
 
   std::regex r("\\n");
 
   std::list<std::string> result;
 
   for (std::sregex_iterator i = query_begin; i != query_end; ++i)
   {
       std::smatch match = *i;
 
       result.push_back(std::regex_replace(match.str(), r, " "));
   }
 
   return result;
}
 
} /* namespace sandbox */
 
#endif // SQL_PARSER_H
 


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: Old от Январь 31, 2024, 10:26
А если запрос получен не полностью?
Например, получили только
"SELECT * FRO"
а остаток прилетит позже?


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: qate от Январь 31, 2024, 10:30
Я пока предполагаю, что это просто plain text в котором могут быть sql запросы.

не похоже https://www.manniwood.com/2016_12_29/tcpdump_pg.html


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: m_ax от Январь 31, 2024, 10:41
А если запрос получен не полностью?
Например, получили только
"SELECT * FRO"
а остаток прилетит позже?

Значит нужен какой то признак того, что данные полученны не полностью/полностью..
Я читаю данные в буфер
Код
C++ (Qt)
char buffer[buff_size];
memset(buffer, 0, buff_size);
int rc = recv(m_fds[i].fd, buffer, buff_size - 1, 0);
 
Как мне узнать, что ответ получен не полностью и ожидается продолжение?


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: Old от Январь 31, 2024, 10:49
Прочитал очередную порцию данный (chunk) добавил к общему буферу.
А дальше уже смотришь, если в общем буфере получена команда полностью (от SELECT и до ';'),  то выкусываем ее из буфера и обрабатываем, иначе ждем оочередной чанк.


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: qate от Январь 31, 2024, 10:50
Как мне узнать, что ответ получен не полностью и ожидается продолжение?

никак - tcp потоковый протокол
для этих целей делают логический пакет "[размер][данные]"



Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: m_ax от Январь 31, 2024, 10:55
Прочитал очередную порцию данный (chunk) добавил к общему буферу.
А дальше уже смотришь, если в общем буфере получена команда полностью (от SELECT и до ';'),  то выкусываем ее из буфера и обрабатываем, иначе ждем оочередной чанк.


никак - tcp потоковый протокол
для этих целей делают логический пакет "[размер][данные]"

Понятно) Спасибо) сегодня будет чем заняться)


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: ssoft от Январь 31, 2024, 10:58
При подключении нового клиента, выполняем подключение к серверу и после этого имеем пару сокетов client-proxy и proxy-server, вот все что приходит по первому сокету, нужно отправить во второй и наоборот. Если один из сокетов закрывается, то нужно закрыть и второй.
Как правило эти два сокета хранятся в объектах, которые называют session. :)

Я тоже к такой схеме пришёл). Назвал объект только proxy_client.

Цитировать
Как мне узнать, что ответ получен не полностью и ожидается продолжение?

Нужно реальный трафик смотреть. В общем случае никак. Если в сети текст, то надеяться, что есть разделитель ";", либо по слову "SELECT" или другим косвенным признакам.
Если бинарный протокол, то его нужно знать, скорее всего, где-то длина пакета должна быть.


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: Old от Январь 31, 2024, 11:00
И еще момент, лучше разделять запросы по ';' не смотря на 'SELECT'.
В SQL есть и другие команды, кроме выборки данных (SELECT). :)


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: ssoft от Январь 31, 2024, 11:10
И еще момент, лучше разделять запросы по ';' не смотря на 'SELECT'.
В SQL есть и другие команды, кроме выборки данных (SELECT). :)

По хорошему да.., но ';' не обязателен в конце запроса. Поэтому про пример трафика и пишу.
Если явных признаков начала и конца нет, то парсер придется писать)).


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: Old от Январь 31, 2024, 11:22
Посмотрел трафик postgresql, там бинарный формат, но очень простой.
Код
C++ (Qt)
uint8_t type;
uint32_t length;
uint8 payload[ length - sizeof( length ) ];
 

type = 0x51 - Simple query
length содержит длину тела, включая само поле length
payload содержит строку запроса 'select * from tab;'

type = 0x54 - Row description
Это ответ на запрос, row идут последовательно один за другим


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: qate от Январь 31, 2024, 11:27
Посмотрел трафик postgresql, там бинарный формат, но очень простой.

еще в помощь https://dev.to/yugabyte/how-to-sniff-postgresql-traffic-3idm

и все это надо сделать как тестовое задание ? )


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: m_ax от Январь 31, 2024, 13:38
Цитировать
и все это надо сделать как тестовое задание ? )
Ну да)
Вот полный текст  :)
Цитировать
Разработать на C++ TCP прокси-сервер для СУБД Postgresql с возможностью логирования
всех SQL запросов, проходящих через него. Документация по сетевому протоколу этой
СУБД доступна на официальном сайте.
Для выполнения тестового задания нужно использовать Berkley sockets (select/poll/epoll).
Прочих зависимостей быть не должно.
Прокси должен уметь обрабатывать большое количество соединений без создания потока
(thread) на каждое соединение. Необходимо распарсить сетевые пакеты, проходящие через
прокси, в которых содержатся SQL запросы, извлечь эти запросы из пакетов и записать их
в файл в виде текста (по одному запросу в строке, структура неважна). Для того, чтобы в
прокси были видны SQL запросы в незашифрованном виде, необходимо отключить SSL
(на клиенте и/или сервере). Должна присутствовать минимальная обработка ошибок, так
же желательны комментарии в тех местах, где возможны ошибки. Приложение не должно
падать на нескольких десятках одновременных соединений, выполняющих запросы к
СУБД без перерыва в течение 5 минут (можно использовать sysbench для тестирования).
Операционная система Linux, компилятор – GCC, так же необходимо создать файл для
сборки проекта с помощью cmake или make.
 
Цель выполнения тестового задания – проверка профессиональных навыков кандидатов
на вакантную позицию. Написанный Вами код не будет использоваться в продуктах
компании или передан третьим лицам. Результат выполнения тестового задания можно
выложить на github.com под любой лицензией во избежание использования Вашего кода.


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: sergek от Январь 31, 2024, 16:18
А если запрос получен не полностью?
Например, получили только
"SELECT * FRO"
а остаток прилетит позже?
А в конце - точка с запятой


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: m_ax от Февраль 03, 2024, 16:42
Всем спасибо за консультацию и советы)
Всё вроде более-менее поправил.
Отправил им на рассмотрение  :) 


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: Old от Февраль 04, 2024, 20:31
Результатов пока нет?  :)


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: m_ax от Февраль 04, 2024, 20:49
Результатов пока нет?  :)
Пока нет) Завтра, наверное, озвучат)  
Но, пожалуй, я в любом случае откажусь, поскольку пока это тестовое задание писал, прошёл другое собеседование.
Показал им свои проекты, там сразу взяли)  
Завтра пойду оформляться  :)


Название: Re: tcp proxy server. Только на berkley sockets (select/poll/epoll)
Отправлено: m_ax от Февраль 06, 2024, 16:24
Результатов пока нет?  :)

Ваше тестовое задание просмотрели, оценили как очень хороший результат.
Как следствие, хотели бы пригласить вас на собеседование.
 :)