Skip to content

Commit

Permalink
PostgreSQL proxy with logging to console
Browse files Browse the repository at this point in the history
  • Loading branch information
qprostu committed Jan 28, 2018
1 parent 1bbd91c commit f48a2f5
Show file tree
Hide file tree
Showing 7 changed files with 387 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ tmp/
*.png
*.ts

tags
.sconsign.dblite
sqlproxy
12 changes: 12 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 2.8)

set(Boost_INCLUDE_DIR /usr/local/include)
set(Boost_LIBRARY_DIR /usr/local/lib)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -pipe -O3 -g -Wall -W -std=c++11")
set(CMAKE_LINK_FLAGS "${CMAKE_LINK_FLAGS} -m64 -g -Wl,-O3")
find_package(Boost COMPONENTS system program_options thread filesystem regex REQUIRED)
include_directories(${Boost_INCLUDE_DIR})
link_directories(${Boost_LIBRARY_DIR})

add_executable(sqlproxy sqlproxy.cpp)
target_link_libraries(sqlproxy ${Boost_LIBRARIES})
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,25 @@ practice any non-privileged port number can be used.
### Protocol version 3.0 documentation:

* [PostgreSQL Frontend/Backend Protocol](https://www.postgresql.org/docs/9.4/static/protocol.html)

### Setup database

Create test role and database

$ psql -U postgres -h localhost
postgres=# CREATE USER test with PASSWORD 'test';
postgres=# CREATE DATABASE testdb WITH ENCODING='UTF8' OWNER=test;

### Build sqlproxy

Commands to build sqlproxy

with scons

$ scons -Q

with cmake:

$ mkdir .build && cd .build
$ cmake ..
$ make
17 changes: 17 additions & 0 deletions SConstruct
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# pylint: disable=invalid-name,undefined-variable
"""
SQLProxy server build configuration
"""
env = Environment(
CPPFLAGS='-m64 -pipe -O3 -g -Wall -W -std=c++11',
LINKFLAGS='-m64 -g -Wl,-O3',
CPPPATH=['/usr/local/include'],
LIBPATH=['/usr/local/lib'],
LIBS=Split('pthread boost_system boost_program_options boost_thread '
'boost_filesystem boost_regex')
)

env['CPPPATH'].append(filter(None, ARGUMENTS.get("cpppath", "").split(",")))
env['LIBPATH'].append(filter(None, ARGUMENTS.get("libpath", "").split(",")))

env.Program('sqlproxy', ['sqlproxy.cpp'])
250 changes: 250 additions & 0 deletions server.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
/**
* server.hpp
*/

#pragma once

#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/bind.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/asio.hpp>
#include <cstdlib>
#include <string>
#include <iostream>
#include <memory>
#include <utility>


namespace ip = boost::asio::ip;

using boost::asio::ip::tcp;


namespace sqlproxy {

/// Class to represent client <-> server session
class session
: public boost::enable_shared_from_this<session>
{
public:
/// Pointer type to session class
typedef boost::shared_ptr<session> ptr_type;

/// Session constructor
session(boost::asio::io_service& ios)
: client_socket_(ios),
server_socket_(ios)
{}

/// Client socket property
tcp::socket& client_socket() {
return client_socket_;
}

void start(const std::string& server_host,
uint16_t server_port) {
/// Connect to PostgreSQL server
server_socket_.async_connect(
tcp::endpoint(
ip::address::from_string(server_host),
server_port),
boost::bind(
&session::on_server_connect,
shared_from_this(),
boost::asio::placeholders::error));
}

private:
/// PostgreSQL server connected
void on_server_connect(
const boost::system::error_code& ec) {
if (!ec) {
auto self(shared_from_this());
// Async read from PostgreSQL server
server_socket_.async_read_some(
boost::asio::buffer(server_data_, max_length),
boost::bind(
&session::on_server_read,
self,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));

// Async read from client
client_socket_.async_read_some(
boost::asio::buffer(client_data_, max_length),
boost::bind(
&session::on_client_read,
self,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else {
close();
}
}

/// Send data from PostgreSQL server to client
void on_server_read(const boost::system::error_code& ec,
const size_t& bytes_transferred) {
if (!ec) {
async_write(
client_socket_,
boost::asio::buffer(server_data_, bytes_transferred),
boost::bind(&session::on_client_write,
shared_from_this(),
boost::asio::placeholders::error));
}
else {
close();
}
}

/// Send data to PostgreSQL server
void on_client_read(const boost::system::error_code& ec,
const size_t& bytes_transferred) {
if (!ec) {
if (client_data_[0] == 'Q') {
// Log sql request to console
std::cout << client_data_[0] << " ";
uint64_t p = (client_data_[1] << 24) +
(client_data_[2] << 16) +
(client_data_[3] << 8) +
client_data_[4];
std::cout << p << " ";
std::string command(
// const_cast<const char*>(&client_data_[5]),
&client_data_[5],
bytes_transferred - 5);
std::cout << command << "\n";
}

async_write(
server_socket_,
boost::asio::buffer(client_data_, bytes_transferred),
boost::bind(&session::on_server_write,
shared_from_this(),
boost::asio::placeholders::error));
}
else {
close();
}
}

/// Async read from PostgreSQL server
void on_client_write(const boost::system::error_code& ec) {
if (!ec) {
server_socket_.async_read_some(
boost::asio::buffer(server_data_, max_length),
boost::bind(
&session::on_server_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else {
close();
}
}

/// Async read from client
void on_server_write(const boost::system::error_code& ec) {
if (!ec) {
client_socket_.async_read_some(
boost::asio::buffer(client_data_, max_length),
boost::bind(
&session::on_client_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else {
close();
}
}

/// Close client and server sockets
void close() {
boost::mutex::scoped_lock lock(mutex_);

if (client_socket_.is_open()) {
client_socket_.close();
}

if (server_socket_.is_open()) {
server_socket_.close();
}
}

tcp::socket client_socket_;
tcp::socket server_socket_;

enum { max_length = 4096 };
std::array<char, max_length> client_data_;
std::array<char, max_length> server_data_;

boost::mutex mutex_;
}; // class session

/// Class to accept connections
class server
{
public:
server(boost::asio::io_service& io_service,
const std::string& local_host, uint16_t local_port,
const std::string& server_host, uint16_t server_port)
: io_service_(io_service),
localhost_address_(
boost::asio::ip::address_v4::from_string(local_host)),
acceptor_(io_service_,
tcp::endpoint(localhost_address_, local_port)),
server_port_(server_port),
server_host_(server_host)
{}

/// Accept client connections
bool accept_connections() {
try {
// Create session with client/server sockets
session_ = boost::make_shared<session>(io_service_);

acceptor_.async_accept(
session_->client_socket(),
boost::bind(&server::on_accept,
this,
boost::asio::placeholders::error));
}
catch (std::exception& e) {
std::cerr << "Server exception: " << e.what() << "\n";
return false;
}

return true;
}

private:
/// On accept connection
void on_accept(const boost::system::error_code& ec) {
if (!ec) {
// Start session
session_->start(server_host_, server_port_);

if (!accept_connections()) {
std::cerr << "Error on call to accept connections.\n";
}
}
else {
std::cerr << "Error: " << ec.message() << "\n";
}
}

boost::asio::io_service& io_service_;
ip::address_v4 localhost_address_;
tcp::acceptor acceptor_;
session::ptr_type session_;
uint16_t server_port_;
std::string server_host_;
}; // class server

} // namespace sqlproxy
6 changes: 6 additions & 0 deletions sqlproxy.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
SQLProxy {
proxy_ip "127.0.0.1"
proxy_port 3333
postgresql_ip "127.0.0.1"
postgresql_port 5432
}
Loading

0 comments on commit f48a2f5

Please sign in to comment.