// // tcpproxy_server_v03.cpp // ~~~~~~~~~~~~~~~~~~~ // // Copyright (c) 2007 Arash Partow (http://www.partow.net) // URL: http://www.partow.net/programming/tcpproxy/index.html // // Distributed under the Boost Software License, Version 1.0. // // // Variation: Upstream and downstream logging. // In this example the upstream and downstream flows will be logged to // disk. The naming convention of the log files will be "(us|ds)_(client ip)_(time).log" // // #include <cstdlib> #include <cstddef> #include <iostream> #include <fstream> #include <string> #include <boost/shared_ptr.hpp> #include <boost/enable_shared_from_this.hpp> #include <boost/bind.hpp> #include <boost/asio.hpp> #include <boost/thread/mutex.hpp> #include <boost/date_time/posix_time/posix_time.hpp> #include <boost/date_time/posix_time/posix_time_io.hpp> namespace tcp_proxy { namespace ip = boost::asio::ip; class bridge : public boost::enable_shared_from_this<bridge> { public: typedef ip::tcp::socket socket_type; typedef boost::shared_ptr<bridge> ptr_type; bridge(boost::asio::io_service& ios) : downstream_socket_(ios), upstream_socket_(ios) {} socket_type& downstream_socket() { return downstream_socket_; } socket_type& upstream_socket() { return upstream_socket_; } void start(const std::string& upstream_host, unsigned short upstream_port) { upstream_socket_.async_connect( ip::tcp::endpoint( boost::asio::ip::address::from_string(upstream_host), upstream_port), boost::bind(&bridge::handle_upstream_connect, shared_from_this(), boost::asio::placeholders::error)); } void handle_upstream_connect(const boost::system::error_code& error) { if (!error) { std::string datetime = format_datetime(boost::posix_time::microsec_clock::universal_time()); std::string upstream_file_name = "us_" + upstream_socket_ .remote_endpoint().address().to_string()+ "_" + datetime + ".log"; std::string downstream_file_name = "ds_" + downstream_socket_.remote_endpoint().address().to_string()+ "_" + datetime + ".log"; upstream_log.open(upstream_file_name.c_str(),std::ios::binary); downstream_log.open(downstream_file_name.c_str(),std::ios::binary); upstream_socket_.async_read_some( boost::asio::buffer(upstream_data_,max_data_length), boost::bind(&bridge::handle_upstream_read, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); downstream_socket_.async_read_some( boost::asio::buffer(downstream_data_,max_data_length), boost::bind(&bridge::handle_downstream_read, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } else close(); } private: void handle_downstream_write(const boost::system::error_code& error) { if (!error) { upstream_socket_.async_read_some( boost::asio::buffer(upstream_data_,max_data_length), boost::bind(&bridge::handle_upstream_read, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } else close(); } void handle_downstream_read(const boost::system::error_code& error, const size_t& bytes_transferred) { if (!error) { if (downstream_log) downstream_log.write(reinterpret_cast<char*>(&downstream_data_[0]),static_cast<std::streamsize>(bytes_transferred)); async_write(upstream_socket_, boost::asio::buffer(downstream_data_,bytes_transferred), boost::bind(&bridge::handle_upstream_write, shared_from_this(), boost::asio::placeholders::error)); } else close(); } void handle_upstream_write(const boost::system::error_code& error) { if (!error) { downstream_socket_.async_read_some( boost::asio::buffer(downstream_data_,max_data_length), boost::bind(&bridge::handle_downstream_read, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } else close(); } void handle_upstream_read(const boost::system::error_code& error, const size_t& bytes_transferred) { if (!error) { if (upstream_log) upstream_log.write(reinterpret_cast<char*>(&upstream_data_[0]),static_cast<std::streamsize>(bytes_transferred)); async_write(downstream_socket_, boost::asio::buffer(upstream_data_,bytes_transferred), boost::bind(&bridge::handle_downstream_write, shared_from_this(), boost::asio::placeholders::error)); } else close(); } void close() { boost::mutex::scoped_lock lock(mutex_); if (downstream_socket_.is_open()) { downstream_socket_.close(); } if (upstream_socket_.is_open()) { upstream_socket_.close(); } downstream_log.close(); upstream_log.close(); } std::string format_datetime(boost::posix_time::ptime pt) { std::string s; std::ostringstream datetime_ss; boost::posix_time::time_facet * p_time_output = new boost::posix_time::time_facet; std::locale special_locale (std::locale(""), p_time_output); datetime_ss.imbue (special_locale); (*p_time_output).format("%Y%m%d%H%M%S.%f"); datetime_ss << pt; s = datetime_ss.str().c_str(); return s; } socket_type downstream_socket_; socket_type upstream_socket_; enum { max_data_length = 8192 }; //8KB unsigned char downstream_data_[max_data_length]; unsigned char upstream_data_[max_data_length]; boost::mutex mutex_; std::ofstream downstream_log; std::ofstream upstream_log; public: class acceptor { public: acceptor(boost::asio::io_service& io_service, const std::string& local_host, unsigned short local_port, const std::string& upstream_host, unsigned short upstream_port) : io_service_(io_service), localhost_address(boost::asio::ip::address_v4::from_string(local_host)), acceptor_(io_service_,ip::tcp::endpoint(localhost_address,local_port)), upstream_port_(upstream_port), upstream_host_(upstream_host) {} bool accept_connections() { try { session_ = boost::shared_ptr<bridge>(new bridge(io_service_)); acceptor_.async_accept(session_->downstream_socket(), boost::bind(&acceptor::handle_accept, this, boost::asio::placeholders::error)); } catch(std::exception& e) { std::cerr << "acceptor exception: " << e.what() << std::endl; return false; } return true; } private: void handle_accept(const boost::system::error_code& error) { if (!error) { session_->start(upstream_host_,upstream_port_); if (!accept_connections()) { std::cerr << "Failure during call to accept." << std::endl; } } else { std::cerr << "Error: " << error.message() << std::endl; } } boost::asio::io_service& io_service_; ip::address_v4 localhost_address; ip::tcp::acceptor acceptor_; ptr_type session_; unsigned short upstream_port_; std::string upstream_host_; }; }; } int main(int argc, char* argv[]) { if (argc != 5) { std::cerr << "usage: tcpproxy_server <local host ip> <local port> <forward host ip> <forward port>" << std::endl; return 1; } const unsigned short local_port = static_cast<unsigned short>(::atoi(argv[2])); const unsigned short forward_port = static_cast<unsigned short>(::atoi(argv[4])); const std::string local_host = argv[1]; const std::string forward_host = argv[3]; boost::asio::io_service ios; try { tcp_proxy::bridge::acceptor acceptor(ios, local_host, local_port, forward_host, forward_port); acceptor.accept_connections(); ios.run(); } catch(std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; return 1; } return 0; } /* * [Note] On posix systems the tcp proxy server build command is as follows: * c++ -pedantic -ansi -Wall -Werror -O3 -o tcpproxy_server_v03 tcpproxy_server_v03.cpp -L/usr/lib -lstdc++ -lpthread -lboost_thread -lboost_system */