├── .gitignore ├── CMakeLists.txt ├── Makefile ├── README.md ├── basic.hpp ├── bridge.hpp ├── client.hpp ├── conanfile.txt ├── controller.hpp ├── main.cpp ├── profiles ├── debug ├── release ├── release-w32 └── release-w64 ├── socks5_server.hpp └── socks5_session.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | lldb* -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5.1) 2 | project(reverse-tunnel) 3 | 4 | #set(CMAKE_CXX_STANDARD 17) 5 | 6 | include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) 7 | 8 | SET(CONAN_LIBS_BOOST boost_program_options boost_system) 9 | conan_basic_setup() 10 | 11 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fcoroutines-ts") 12 | 13 | add_executable(reverse-tunnel main.cpp) 14 | target_link_libraries(reverse-tunnel ${CONAN_LIBS}) 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXX ?= clang++ 2 | 3 | .PHONY: release debug lldb 4 | release: 5 | mkdir -p build && \ 6 | cd build && \ 7 | conan install .. --profile ../profiles/release && \ 8 | cmake .. -DCMAKE_BUILD_TYPE=Release && \ 9 | cmake --build . 10 | 11 | debug: lldb 12 | echo launching raw 13 | 14 | clean: 15 | rm -rf build 16 | 17 | lldb: 18 | $(CXX) *.cpp -glldb -o lldb -std=c++17 -fcoroutines-ts -lboost_program_options -lboost_system 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reverse-tunnel-cpp 2 | A Small program punch through NAT firewalls 3 | 4 | This project is only a proof of concept, which is not suitable for running for a long period. 5 | Please use [frp](https://github.com/fatedier/frp). It is better documented, and the features are much more richer than this project. 6 | 7 | The command option are: 8 | 9 | ``` 10 | --help, -h Print this help messages 11 | --srv [server mode] listen port, default value: 7000. 12 | --connect, -c [export mode] connect to server. 13 | --export, -e [export mode] export server endpoint. 14 | --bind, -b [export mode] bind remote server. 15 | --socks5, -s [socks5 mode] start socks5 server on this port. 16 | ``` 17 | 18 | 19 | For example: 20 | ``` 21 | ./reverse-tunnel --srv :7000 22 | ``` 23 | Running reverse-tunnel on port :7000 24 | 25 | ``` 26 | ./reverse-tunnel --connect 127.0.0.1:7000 --bind :8000 --export localhost:8080 27 | ``` 28 | connect to remote server `127.0.0.1:7000`, request to bind on `:8000` on the remote server, and export my `localhost:8080` service. 29 | -------------------------------------------------------------------------------- /basic.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BASIC_HPP_ 2 | #define BASIC_HPP_ 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace pika 13 | { 14 | 15 | namespace lib { 16 | 17 | using boost::asio::ip::tcp; 18 | using boost::asio::experimental::co_spawn; 19 | using boost::asio::experimental::detached; 20 | namespace this_coro = boost::asio::experimental::this_coro; 21 | 22 | template 23 | using awaitable = boost::asio::experimental::awaitable; 24 | 25 | }// namespace lib 26 | 27 | namespace def 28 | { 29 | 30 | constexpr int bufsize = 4 * 1024; 31 | 32 | }// namespace def 33 | 34 | namespace util 35 | { 36 | 37 | inline 38 | std::size_t hash(boost::asio::ip::tcp::endpoint const &e) 39 | { 40 | std::ostringstream stream; 41 | stream << e; 42 | std::hash hasher; 43 | return hasher(stream.str()); 44 | } 45 | 46 | inline 47 | lib::tcp::endpoint make_connectable(std::string_view host, boost::asio::io_context &io_context) 48 | { 49 | auto it = std::find(host.begin(), host.end(), ':'); 50 | std::string_view server_host = host.substr(0, std::distance(host.begin(), it)); 51 | std::string_view server_port = host.substr(std::distance(host.begin(), it) + 1, std::distance(it, host.end()) - 1); 52 | 53 | lib::tcp::resolver resolver{io_context}; 54 | if (server_host.empty()) 55 | server_host = "0.0.0.0"; 56 | return *resolver.resolve(server_host, server_port); 57 | } 58 | 59 | }// namespace util 60 | 61 | namespace error 62 | { 63 | using namespace std::chrono_literals; 64 | class restart_request : public std::exception 65 | { 66 | constexpr static std::chrono::seconds max_waittime_ {600s}; 67 | std::chrono::seconds waittime_ {600s}; 68 | bool empty_ {true}; 69 | public: 70 | restart_request() = default; 71 | restart_request(std::chrono::seconds time): 72 | waittime_{(time > max_waittime_? max_waittime_ : time)}, 73 | empty_{false} {} 74 | 75 | virtual char const * what() const noexcept override { return "Restart Requested\n"; } 76 | void sleep() const {std::this_thread::sleep_for(waittime_);} 77 | operator bool() {return not empty_;}; 78 | }; 79 | } // namespace error 80 | 81 | }// namespace pika 82 | 83 | #endif // BASIC_HPP_ 84 | -------------------------------------------------------------------------------- /bridge.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BRIDGE_HPP_ 2 | #define BRIDGE_HPP_ 3 | 4 | namespace pika 5 | { 6 | 7 | class bridge : public std::enable_shared_from_this 8 | { 9 | public: 10 | lib::tcp::socket first_socket_; 11 | lib::tcp::socket second_socket_; 12 | 13 | bridge (lib::tcp::socket && f, lib::tcp::socket && s) : 14 | first_socket_{std::move(f)}, 15 | second_socket_{std::move(s)} {} 16 | 17 | lib::awaitable start_transport() 18 | { 19 | auto self = shared_from_this(); 20 | auto executor = co_await lib::this_coro::executor(); 21 | 22 | lib::co_spawn(executor, 23 | [self]() mutable { 24 | return self->redir(self->first_socket_, 25 | self->second_socket_); 26 | }, lib::detached); 27 | lib::co_spawn(executor, 28 | [self]() mutable { 29 | return self->redir(self->second_socket_, 30 | self->first_socket_); 31 | }, lib::detached); 32 | } 33 | 34 | private: 35 | lib::awaitable redir(lib::tcp::socket &from, lib::tcp::socket &to) 36 | { 37 | auto executor = co_await lib::this_coro::executor(); 38 | auto token = co_await lib::this_coro::token(); 39 | auto self = shared_from_this(); 40 | try 41 | { 42 | std::array raw_buf; 43 | for (;;) 44 | { 45 | std::size_t read_n = co_await from.async_read_some(boost::asio::buffer(raw_buf), token); 46 | std::ignore = co_await boost::asio::async_write(to, boost::asio::buffer(raw_buf, read_n), token); 47 | } 48 | } 49 | catch (boost::system::system_error const & e) 50 | { 51 | if (e.code() != boost::asio::error::eof) 52 | std::cerr << "bridge::redir() exception: " << e.what() << std::endl; 53 | } 54 | catch (std::exception const &e) 55 | { 56 | std::cerr << "bridge::redir() std exception: " << e.what() << std::endl; 57 | } 58 | boost::system::error_code ec; 59 | from.shutdown(lib::tcp::socket::shutdown_both, ec); 60 | to.shutdown(lib::tcp::socket::shutdown_both, ec); 61 | co_return; 62 | } 63 | }; 64 | 65 | } // namespace pika 66 | 67 | #endif // BRIDGE_HPP_ 68 | -------------------------------------------------------------------------------- /client.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CLIENT_HPP_ 2 | #define CLIENT_HPP_ 3 | 4 | #pragma once 5 | 6 | #include "basic.hpp" 7 | 8 | namespace pika 9 | { 10 | 11 | class client : public std::enable_shared_from_this 12 | { 13 | lib::tcp::endpoint export_ep_; 14 | lib::tcp::endpoint controller_ep_; 15 | 16 | public: 17 | client(std::string_view export_host, boost::asio::io_context &io_context): 18 | export_ep_{util::make_connectable(export_host, io_context)} {} 19 | 20 | lib::awaitable run(std::string_view controller_host, 21 | std::string_view controller_bind, 22 | error::restart_request &req) 23 | { 24 | auto executor = co_await lib::this_coro::executor(); 25 | auto token = co_await lib::this_coro::token(); 26 | 27 | try 28 | { 29 | auto self = shared_from_this(); 30 | 31 | self->controller_ep_ = util::make_connectable(controller_host, executor.context()); 32 | lib::tcp::socket controller_socket{executor.context()}; 33 | co_await controller_socket.async_connect(self->controller_ep_, token); 34 | 35 | auto controller_bind_ep = util::make_connectable(controller_bind, executor.context()); 36 | { // send bind request 37 | std::uint32_t ip = controller_bind_ep.address().to_v4().to_ulong(); 38 | boost::endian::native_to_big_inplace(ip); 39 | std::uint16_t port = controller_bind_ep.port(); 40 | boost::endian::native_to_big_inplace(port); 41 | 42 | std::array req{0x01, 0x00}; 43 | std::memcpy(&req[2] , &ip, sizeof ip); 44 | std::memcpy(&req[2 + sizeof ip], &port, sizeof port); 45 | std::ignore = co_await boost::asio::async_write(controller_socket, boost::asio::buffer(req), token); 46 | } 47 | 48 | for (;;) 49 | { 50 | std::array buf{}; 51 | std::size_t length = co_await boost::asio::async_read(controller_socket, boost::asio::buffer(buf), token); 52 | 53 | if (buf.at(1) != 0) 54 | { 55 | std::cout << "Error connecting to remote server\n"; 56 | using namespace std::chrono_literals; 57 | throw error::restart_request{1s}; 58 | } 59 | else 60 | { 61 | switch(buf.at(0)) 62 | { 63 | case 0x00: // do nothing 64 | break; 65 | case 0x02: // Is remote request 66 | { 67 | std::uint32_t id = 0; 68 | std::memcpy(&id, &buf[2], 4); 69 | lib::co_spawn(executor, 70 | [self, id]() mutable { 71 | return self->make_bridge(id); 72 | }, lib::detached); 73 | break; 74 | } 75 | default: 76 | // response failed 77 | break; 78 | } 79 | } 80 | } 81 | } 82 | catch (error::restart_request const & e) 83 | { 84 | std::cerr << "client restart exception, restarting\n"; 85 | executor.context().stop(); 86 | req = e; 87 | co_return; 88 | } 89 | catch (std::exception const & e) 90 | { 91 | std::cerr << "client::start() exception: " << e.what() << std::endl; 92 | executor.context().stop(); 93 | } 94 | 95 | // this is temparary 96 | using namespace std::chrono_literals; 97 | req = error::restart_request{1s}; 98 | } 99 | 100 | lib::awaitable make_bridge(std::uint32_t const id) 101 | { 102 | try 103 | { 104 | auto executor = co_await lib::this_coro::executor(); 105 | auto token = co_await lib::this_coro::token(); 106 | auto self = shared_from_this(); 107 | 108 | auto proxy_bridge = std::make_shared(lib::tcp::socket{executor.context()}, 109 | lib::tcp::socket{executor.context()}); 110 | lib::tcp::socket 111 | & export_socket{proxy_bridge->first_socket_}, 112 | & controller_socket{proxy_bridge->second_socket_}; 113 | co_await export_socket.async_connect(self->export_ep_, token); 114 | co_await controller_socket.async_connect(self->controller_ep_, token); 115 | std::array req{0x02, 0x00}; 116 | std::memcpy(&req[2], &id, sizeof id); 117 | std::ignore = co_await boost::asio::async_write(controller_socket, boost::asio::buffer(req), token); 118 | co_await proxy_bridge->start_transport(); 119 | } 120 | catch (std::exception const & e) 121 | { 122 | std::cerr << "client::make_bridge() exception: " << e.what() << std::endl; 123 | } 124 | } 125 | }; 126 | 127 | } 128 | 129 | #endif // CLIENT_HPP_ 130 | -------------------------------------------------------------------------------- /conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | boost/1.68.0@conan/stable 3 | 4 | [generators] 5 | cmake 6 | -------------------------------------------------------------------------------- /controller.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CONTROLLER_HPP_ 2 | #define CONTROLLER_HPP_ 3 | 4 | #include 5 | #include 6 | #include "basic.hpp" 7 | 8 | namespace pika 9 | { 10 | 11 | class controller 12 | { 13 | lib::tcp::endpoint listen_ep_; 14 | std::unordered_map clients; 15 | public: 16 | controller(std::string_view listen_host, boost::asio::io_context &io_context): 17 | listen_ep_{util::make_connectable(listen_host, io_context)} {} 18 | 19 | lib::awaitable run() 20 | { 21 | auto executor = co_await lib::this_coro::executor(); 22 | auto token = co_await lib::this_coro::token(); 23 | 24 | lib::tcp::acceptor acceptor {executor.context(), listen_ep_}; 25 | std::cout << "start listining on " << listen_ep_ << "\n"; 26 | for (;;) 27 | { 28 | lib::tcp::socket socket = co_await acceptor.async_accept(token); 29 | lib::co_spawn(executor, 30 | [socket = std::move(socket), this]() mutable { 31 | return init_session(std::move(socket)); 32 | }, lib::detached); 33 | } 34 | } 35 | private: 36 | lib::awaitable init_session(lib::tcp::socket && socket) 37 | { 38 | auto executor = co_await lib::this_coro::executor(); 39 | auto token = co_await lib::this_coro::token(); 40 | 41 | std::array buf; 42 | std::size_t length = co_await boost::asio::async_read(socket, boost::asio::buffer(buf), token); 43 | assert(length == 8); 44 | 45 | switch(buf.at(0)) 46 | { 47 | case 0x00: // do nothing 48 | break; 49 | case 0x01: // Request bind port 50 | { 51 | std::uint32_t ipv4 = 0; 52 | std::memcpy(&ipv4, &buf[2], sizeof ipv4); 53 | boost::endian::big_to_native_inplace(ipv4); 54 | 55 | std::uint16_t port = 0; 56 | std::memcpy(&port, &buf[2 + sizeof ipv4], sizeof port); 57 | boost::endian::big_to_native_inplace(port); 58 | 59 | lib::co_spawn(executor, 60 | [socket = std::move(socket), ipv4, port, this]() mutable { 61 | boost::asio::socket_base::keep_alive opt{true}; 62 | socket.set_option(opt); 63 | return start_reverse_tunnel(std::move(socket), ipv4, port); 64 | }, lib::detached); 65 | break; 66 | } 67 | case 0x02: // Connect with id 68 | { 69 | std::uint32_t id = 0; 70 | std::memcpy(&id, &buf[2], sizeof id); 71 | boost::endian::big_to_native_inplace(id); 72 | lib::co_spawn(executor, 73 | [socket = std::move(socket), id, this]() mutable { 74 | return start_bridge(std::move(socket), id); 75 | }, lib::detached); 76 | break; 77 | } 78 | default: 79 | { 80 | // response failed 81 | std::array response{0x00 /* CONNECT */, 0x01 /* FAILED */}; 82 | std::ignore = co_await boost::asio::async_write(socket, boost::asio::buffer(response), token); 83 | break; 84 | } 85 | } 86 | } 87 | 88 | lib::awaitable start_reverse_tunnel(lib::tcp::socket && remote_socket, 89 | std::uint32_t ip, std::uint16_t port) 90 | { 91 | auto executor = co_await lib::this_coro::executor(); 92 | auto token = co_await lib::this_coro::token(); 93 | 94 | try 95 | { 96 | lib::tcp::endpoint ep{boost::asio::ip::address_v4{ip}, port}; 97 | lib::tcp::acceptor acceptor(executor.context(), ep); 98 | std::cout << "reverse tunnel start listening on " << ep << "\n"; 99 | BOOST_SCOPE_EXIT (&ep) { 100 | std::cout << "reverse tunnel closed listening on " << ep << "\n"; 101 | } BOOST_SCOPE_EXIT_END; 102 | 103 | lib::co_spawn(executor, 104 | [&remote_socket, &acceptor]() mutable { 105 | return monitor_socket(remote_socket, acceptor); 106 | }, lib::detached); 107 | 108 | for (;;) 109 | { 110 | lib::tcp::socket socket = co_await acceptor.async_accept(token); 111 | std::uint32_t address = util::hash(socket.remote_endpoint()); 112 | clients.insert({address, std::move(socket)}); 113 | 114 | std::array response{0x02}; 115 | boost::endian::native_to_big_inplace(address); 116 | std::memcpy(&response[2], &address, sizeof address); 117 | 118 | std::ignore = co_await boost::asio::async_write(remote_socket, boost::asio::buffer(response), token); 119 | } 120 | } 121 | catch (std::exception const & e) 122 | { 123 | std::array response{0x02 /* CONNECT */, 0x01 /* FAILED */}; 124 | std::ignore = co_await boost::asio::async_write(remote_socket, boost::asio::buffer(response), token); 125 | std::cerr << "controller::start_reverse_tunnel exception: " << e.what() << std::endl; 126 | } 127 | } 128 | 129 | static 130 | lib::awaitable monitor_socket(lib::tcp::socket & remote, lib::tcp::acceptor & acceptor) 131 | { 132 | std::array keep_alive{}; 133 | auto executor = co_await lib::this_coro::executor(); 134 | auto token = co_await lib::this_coro::token(); 135 | using namespace std::literals; 136 | 137 | try 138 | { 139 | for (;;) 140 | { 141 | boost::asio::steady_timer t{executor.context(), 10s}; 142 | co_await t.async_wait(token); 143 | std::ignore = co_await boost::asio::async_write(remote, boost::asio::buffer(keep_alive), token); 144 | } 145 | } 146 | catch(boost::system::system_error const & e) 147 | { 148 | if (e.code() != boost::asio::error::eof || 149 | e.code() != boost::asio::error::broken_pipe) 150 | std::cerr << "controller::monitor_socket exception: " << e.what() << std::endl; 151 | acceptor.cancel(); 152 | acceptor.close(); 153 | } 154 | } 155 | 156 | lib::awaitable start_bridge(lib::tcp::socket && s, std::uint32_t id) 157 | { 158 | try 159 | { 160 | lib::tcp::socket &local = clients.at(id); 161 | auto b = std::make_shared(std::move(s), std::move(local)); 162 | controller * self = this; 163 | BOOST_SCOPE_EXIT (self, id) { 164 | self->clients.erase(id); 165 | } BOOST_SCOPE_EXIT_END; 166 | co_await b->start_transport(); 167 | } 168 | catch (std::exception const & e) 169 | { 170 | std::cerr << "controller::start_bridge exception: " << e.what() << std::endl; 171 | } 172 | } 173 | }; 174 | 175 | }// namespace pika 176 | 177 | #endif // CONTROLLER_HPP_ 178 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "socks5_server.hpp" 4 | #include "controller.hpp" 5 | #include "client.hpp" 6 | 7 | int main(int argc, char *argv[]) 8 | { 9 | try 10 | { 11 | namespace po = boost::program_options; 12 | enum class mode { 13 | srv, 14 | exp, 15 | socks5 16 | }; 17 | mode run_mode {mode::srv}; 18 | std::string srv_listen_host, socks5_listen_host, connect_host, export_host, bind_host; 19 | std::unique_ptr socks5_server_endpoint_socket {nullptr}; 20 | boost::asio::io_context io_context; 21 | 22 | po::options_description desc{"Options"}; 23 | desc.add_options() 24 | ("help,h", "Print this help messages") 25 | ("srv", po::value(&srv_listen_host)->default_value(":7000"), "[server mode] listen port") 26 | ("connect,c", po::value(), "[export mode] connect to server") 27 | ("export,e", po::value(), "[export mode] export server endpoint") 28 | ("bind,b", po::value(), "[export mode] bind remote server") 29 | ("socks5,s", po::value(), "[socks5 mode] start socks5 server on this port"); 30 | po::positional_options_description pos_po; 31 | po::variables_map vm; 32 | 33 | po::store(po::command_line_parser(argc, argv) 34 | .options(desc) 35 | .positional(pos_po).run(), 36 | vm); 37 | po::notify(vm); 38 | if (vm.count("connect") || vm.count("export") || vm.count("bind")) 39 | { 40 | run_mode = mode::exp; 41 | if ((!! vm.count("connect")) ^ (!! vm.count("bind"))) 42 | { 43 | std::cerr << "[export mode] --connect and and --bind must spectify at the same time\n"; 44 | std::exit(1); 45 | } 46 | 47 | connect_host = vm["connect"].as(); 48 | bind_host = vm["bind"].as(); 49 | 50 | if (not vm.count("export")) 51 | { 52 | std::cout << "[export mode] --export not present, using internal socks5 server\n"; 53 | socks5_server_endpoint_socket = std::make_unique(io_context, 54 | pika::lib::tcp::endpoint{pika::lib::tcp::v4(), 0}); 55 | 56 | using namespace std::literals; 57 | export_host = "127.0.0.1:"s + std::to_string(socks5_server_endpoint_socket->local_endpoint().port()); 58 | } 59 | else 60 | export_host = vm["export"].as(); 61 | } 62 | else if (vm.count("socks5")) 63 | { 64 | run_mode = mode::socks5; 65 | socks5_listen_host = vm["socks5"].as(); 66 | } 67 | else 68 | run_mode = mode::srv; 69 | 70 | boost::asio::signal_set signals{io_context, SIGINT, SIGTERM}; 71 | signals.async_wait([&](auto, auto){ io_context.stop(); }); 72 | 73 | bool restart{true}; 74 | while (restart) 75 | { 76 | pika::error::restart_request req; 77 | restart = false; 78 | 79 | switch (run_mode) 80 | { 81 | case mode::socks5: 82 | { 83 | std::cout << "[socks5 mode] "; 84 | pika::socks5::server server{socks5_listen_host, io_context}; 85 | pika::lib::co_spawn(io_context, 86 | [&server] { 87 | return server.run(); 88 | }, pika::lib::detached); 89 | io_context.run(); 90 | break; 91 | } 92 | case mode::srv: 93 | { 94 | pika::controller server{srv_listen_host, io_context}; 95 | pika::lib::co_spawn(io_context, 96 | [&server] { 97 | return server.run(); 98 | }, pika::lib::detached); 99 | io_context.run(); 100 | break; 101 | } 102 | case mode::exp: 103 | { 104 | // if socks5_server_endpoint_socket not null, start socks5 server 105 | if (socks5_server_endpoint_socket) 106 | { 107 | std::thread t( 108 | [socket = std::move(socks5_server_endpoint_socket), &export_host]() mutable 109 | { 110 | // retrive the random port from socket, release it, than bind it again 111 | using namespace std::literals; 112 | std::cout << "Starting socks5 server at " << export_host << "\n"; 113 | socket.reset(); 114 | boost::asio::io_context io; 115 | pika::socks5::server server {export_host, io}; 116 | pika::lib::co_spawn(io, 117 | [&server] { 118 | return server.run(); 119 | }, pika::lib::detached); 120 | io.run(); 121 | }); 122 | t.detach(); 123 | } 124 | auto c = std::make_shared(export_host, io_context); 125 | pika::lib::co_spawn(io_context, 126 | [&c, &connect_host, &bind_host, &req] { 127 | return c->run(connect_host, bind_host, req); 128 | }, pika::lib::detached); 129 | io_context.run(); 130 | break; 131 | } 132 | } 133 | 134 | if (req) 135 | { 136 | io_context.restart(); 137 | std::cout << "Retrying ... first sleep.\n"; 138 | req.sleep(); 139 | restart = true; 140 | } 141 | } 142 | } 143 | catch (const std::exception& e) 144 | { 145 | std::cerr << "Exception: " << e.what() << std::endl; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /profiles/debug: -------------------------------------------------------------------------------- 1 | include(default) 2 | 3 | [settings] 4 | cppstd=17 5 | build_type=Debug -------------------------------------------------------------------------------- /profiles/release: -------------------------------------------------------------------------------- 1 | include(default) 2 | 3 | [settings] 4 | cppstd=17 5 | build_type=Release -------------------------------------------------------------------------------- /profiles/release-w32: -------------------------------------------------------------------------------- 1 | include(clangx32) 2 | 3 | [settings] 4 | cppstd=17 5 | build_type=Release 6 | 7 | [options] 8 | boost:fPIC=False 9 | boost:shared=False 10 | boost:without_math=True 11 | boost:without_wave=True 12 | boost:without_container=True 13 | boost:without_contract=True 14 | boost:without_exception=True 15 | boost:without_graph=True 16 | boost:without_iostreams=True 17 | boost:without_locale=True 18 | boost:without_log=True 19 | boost:without_random=True 20 | boost:without_regex=True 21 | boost:without_mpi=True 22 | boost:without_serialization=True 23 | boost:without_signals=True 24 | boost:without_coroutine=True 25 | boost:without_fiber=True 26 | boost:without_context=True 27 | boost:without_timer=True 28 | boost:without_thread=True 29 | boost:without_chrono=True 30 | boost:without_date_time=True 31 | boost:without_atomic=True 32 | boost:without_filesystem=True 33 | boost:without_graph_parallel=True 34 | boost:without_python=True 35 | boost:without_stacktrace=True 36 | boost:without_test=True 37 | boost:without_type_erasure=True -------------------------------------------------------------------------------- /profiles/release-w64: -------------------------------------------------------------------------------- 1 | include(clangx64) 2 | 3 | [settings] 4 | cppstd=17 5 | build_type=Release 6 | 7 | [options] 8 | boost:fPIC=False 9 | boost:shared=False 10 | boost:without_math=True 11 | boost:without_wave=True 12 | boost:without_container=True 13 | boost:without_contract=True 14 | boost:without_exception=True 15 | boost:without_graph=True 16 | boost:without_iostreams=True 17 | boost:without_locale=True 18 | boost:without_log=True 19 | boost:without_random=True 20 | boost:without_regex=True 21 | boost:without_mpi=True 22 | boost:without_serialization=True 23 | boost:without_signals=True 24 | boost:without_coroutine=True 25 | boost:without_fiber=True 26 | boost:without_context=True 27 | boost:without_timer=True 28 | boost:without_thread=True 29 | boost:without_chrono=True 30 | boost:without_date_time=True 31 | boost:without_atomic=True 32 | boost:without_filesystem=True 33 | boost:without_graph_parallel=True 34 | boost:without_python=True 35 | boost:without_stacktrace=True 36 | boost:without_test=True 37 | boost:without_type_erasure=True -------------------------------------------------------------------------------- /socks5_server.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SOCKS5_SERVER_HPP_ 2 | #define SOCKS5_SERVER_HPP_ 3 | 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | #include "socks5_session.hpp" 9 | 10 | namespace pika::socks5 11 | { 12 | 13 | class server 14 | { 15 | lib::tcp::endpoint listen_ep_; 16 | lib::tcp::resolver::results_type target_server_ep_; 17 | public: 18 | server(std::string_view listen_host, boost::asio::io_context &io_context): 19 | listen_ep_{util::make_connectable(listen_host, io_context)} {} 20 | 21 | lib::awaitable run() 22 | { 23 | auto executor = co_await lib::this_coro::executor(); 24 | auto token = co_await lib::this_coro::token(); 25 | 26 | lib::tcp::acceptor acceptor{executor.context(), listen_ep_}; 27 | std::cout << "socks5 server start listining on " << listen_ep_ << "\n"; 28 | for (;;) 29 | { 30 | lib::tcp::socket socket = co_await acceptor.async_accept(token); 31 | lib::co_spawn(executor, 32 | [socket = std::move(socket)]() mutable 33 | { 34 | return std::make_shared(std::move(socket))->start(); 35 | }, 36 | lib::detached); 37 | } 38 | } 39 | }; 40 | 41 | }// namespace pika::socks5 42 | 43 | 44 | #endif // SOCKS5_SERVER_HPP_ 45 | -------------------------------------------------------------------------------- /socks5_session.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SOCKS5_SESSION_HPP_ 2 | #define SOCKS5_SESSION_HPP_ 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include "basic.hpp" 10 | #include "bridge.hpp" 11 | 12 | namespace pika::socks5 13 | { 14 | 15 | class session : public std::enable_shared_from_this 16 | { 17 | boost::asio::io_context &io_; 18 | std::shared_ptr bridge_; 19 | lib::tcp::socket& socket_; 20 | lib::tcp::socket& target_socket_; 21 | public: 22 | session(lib::tcp::socket && client): 23 | io_{client.get_executor().context()}, 24 | bridge_{std::make_shared(std::move(client), 25 | lib::tcp::socket{io_})}, 26 | socket_{bridge_->first_socket_}, 27 | target_socket_{bridge_->second_socket_} {} 28 | 29 | lib::awaitable start() 30 | { 31 | try 32 | { 33 | auto self = shared_from_this(); 34 | auto token = co_await lib::this_coro::token(); 35 | auto executor = co_await lib::this_coro::executor(); 36 | 37 | 38 | { // socks5 handshake 39 | /* 40 | +----+----------+----------+ 41 | |VER | NMETHODS | METHODS | 42 | +----+----------+----------+ 43 | | 1 | 1 | 1 to 255 | 44 | +----+----------+----------+ 45 | o X'00' NO AUTHENTICATION REQUIRED 46 | o X'01' GSSAPI 47 | o X'02' USERNAME/PASSWORD 48 | o X'03' to X'7F' IANA ASSIGNED 49 | o X'80' to X'FE' RESERVED FOR PRIVATE METHODS 50 | o X'FF' NO ACCEPTABLE METHODS 51 | */ 52 | std::array buf; // buf contains {VER, NMETHODS} 53 | std::size_t length = co_await boost::asio::async_read(socket_, boost::asio::buffer(buf), token); 54 | assert(length == 2); 55 | 56 | if (buf[0] != 0x05) 57 | throw std::runtime_error("Protocol mismatch"); 58 | 59 | std::uint8_t num = buf[1]; 60 | std::vector nmethod_buf(num); 61 | length = co_await boost::asio::async_read(socket_, boost::asio::buffer(nmethod_buf), token); 62 | assert(length == num); 63 | 64 | std::array response{{0x05, 0xFF}}; 65 | 66 | for (int i = 0; i < num; i++) 67 | if (nmethod_buf.at(i) == 0x00 /* NO AUTHENTICATION REQUIRED */) 68 | response[1] = 0x00; 69 | 70 | std::ignore = co_await boost::asio::async_write(socket_, boost::asio::buffer(response), token); 71 | } // socks5 handshake end 72 | 73 | lib::tcp::endpoint target_endpoint; 74 | { // socks5 request 75 | /* array head{VER, CMD, RSV, ATYP} 76 | +----+-----+-------+------+----------+----------+ 77 | |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 78 | +----+-----+-------+------+----------+----------+ 79 | | 1 | 1 | X'00' | 1 | Variable | 2 | 80 | +----+-----+-------+------+----------+----------+ 81 | o VER protocol version: X'05' 82 | o CMD 83 | o CONNECT X'01' 84 | o BIND X'02' 85 | o UDP ASSOCIATE X'03' 86 | o RSV RESERVED 87 | o ATYP address type of following address 88 | o IP V4 address: X'01' 89 | o DOMAINNAME: X'03' 90 | o IP V6 address: X'04' 91 | o DST.ADDR desired destination address 92 | o DST.PORT desired destination port in network octet 93 | order 94 | */ 95 | std::array head{}; // field {VER, CMD, RSV, ATYP} 96 | std::size_t length = co_await boost::asio::async_read(socket_, boost::asio::buffer(head), token); 97 | assert(length == 4); 98 | enum { 99 | VER = 0, 100 | CMD, 101 | RSV, 102 | ATYP 103 | }; 104 | 105 | if (head[VER] != 0x05 || 106 | head[CMD] != 0x01 /* CONNECT */) 107 | throw std::runtime_error("socks5 request invalid"); 108 | 109 | int constexpr port_length = 2; 110 | switch (head[ATYP]) 111 | { 112 | case 0x01 /* IP v4 */: 113 | { 114 | // version-4 IP address, with a length of 4 octets 115 | std::array buf{}; 116 | length = co_await boost::asio::async_read(socket_, boost::asio::buffer(buf), token); 117 | assert(length == 4 + port_length); 118 | std::uint32_t n_ip = 0; 119 | static_assert(sizeof(std::uint32_t) == 4); 120 | std::memcpy(&n_ip, buf.data(), sizeof n_ip); 121 | boost::endian::big_to_native_inplace(n_ip); 122 | boost::asio::ip::address_v4 ipv4{n_ip}; 123 | 124 | std::uint16_t n_port = 0; 125 | std::memcpy(&n_port, buf.data() + 4, port_length); 126 | boost::endian::big_to_native_inplace(n_port); 127 | 128 | target_endpoint = lib::tcp::endpoint(ipv4, n_port); 129 | break; 130 | } 131 | case 0x03 /* domain name */: 132 | { 133 | // get length of domain name 134 | std::array buf{}; 135 | std::ignore = co_await boost::asio::async_read(socket_, boost::asio::buffer(buf), token); 136 | int domain_name_length = buf[0]; 137 | std::vector domain(domain_name_length + port_length); 138 | length = co_await boost::asio::async_read(socket_, boost::asio::buffer(domain), token); 139 | assert(length == domain_name_length + port_length); 140 | 141 | std::uint16_t n_port = 0; 142 | std::memcpy(&n_port, domain.data() + domain_name_length, port_length); 143 | boost::endian::big_to_native_inplace(n_port); 144 | 145 | std::string domain_name; 146 | std::copy_n (domain.data(), domain_name_length, std::back_inserter(domain_name)); 147 | 148 | lib::tcp::resolver resolver{socket_.get_executor().context()}; 149 | lib::tcp::resolver::results_type r = resolver.resolve(domain_name, std::to_string(n_port)); 150 | 151 | target_endpoint = *r; 152 | break; 153 | } 154 | case 0x04 /* IP v6 */: 155 | { 156 | // version-4 IP address, with a length of 16 octets 157 | std::array buf{}; 158 | std::cerr << "IP v6 not impl\n"; 159 | [[fallthrough]]; 160 | } 161 | default: 162 | throw std::runtime_error("ATYP not supported"); 163 | } 164 | std::cout << "socks5 session #" << self->id() << " started with target endpoint: " << target_endpoint << "\n"; 165 | } // socks5 request end 166 | 167 | { // response of socks5 request 168 | /* 169 | +----+-----+-------+------+----------+----------+ 170 | |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 171 | +----+-----+-------+------+----------+----------+ 172 | | 1 | 1 | X'00' | 1 | Variable | 2 | 173 | +----+-----+-------+------+----------+----------+ 174 | o VER protocol version: X'05' 175 | o REP Reply field: 176 | o X'00' succeeded 177 | o X'01' general SOCKS server failure 178 | o X'02' connection not allowed by ruleset 179 | o X'03' Network unreachable 180 | o X'04' Host unreachable 181 | o X'05' Connection refused 182 | o X'06' TTL expired 183 | o X'07' Command not supported 184 | o X'08' Address type not supported 185 | o X'09' to X'FF' unassigned 186 | o RSV RESERVED 187 | o ATYP address type of following address 188 | o IP V4 address: X'01' 189 | o DOMAINNAME: X'03' 190 | o IP V6 address: X'04' 191 | o BND.ADDR server bound address 192 | o BND.PORT server bound port in network octet order 193 | */ 194 | std::array response{{ 198 | 0x05, // VER 199 | 0x00, // REP 200 | 0x00, // RSV 201 | 0x01, // ATYP == ipv4 202 | }}; 203 | try 204 | { 205 | co_await target_socket_.async_connect(target_endpoint, token); 206 | 207 | std::uint32_t ip = target_socket_.remote_endpoint().address().to_v4().to_ulong(); 208 | boost::endian::native_to_big_inplace(ip); 209 | std::uint16_t port = target_socket_.remote_endpoint().port(); 210 | boost::endian::native_to_big_inplace(port); 211 | 212 | std::memcpy(&response[4], &ip, sizeof ip); 213 | std::memcpy(&response[8], &port, sizeof port); 214 | } 215 | catch (boost::system::system_error const & e) 216 | { 217 | switch (e.code().value()) 218 | { 219 | case boost::asio::error::network_unreachable: response[1] = 0x03; break; 220 | case boost::asio::error::host_unreachable: response[1] = 0x04; break; 221 | case boost::asio::error::connection_refused: response[1] = 0x05; break; 222 | case boost::asio::error::timed_out: response[1] = 0x06; break; 223 | default: response[1] = 0x01; break; 224 | } 225 | std::ignore = co_await boost::asio::async_write(socket_, boost::asio::buffer(response), token); 226 | 227 | using namespace std::literals; 228 | throw std::runtime_error("Bad target endpoint, error: "s + e.what()); 229 | co_return; 230 | } 231 | std::ignore = co_await boost::asio::async_write(socket_, boost::asio::buffer(response), token); 232 | } // response of socks5 request end 233 | 234 | co_await self->bridge_->start_transport(); 235 | } 236 | catch (std::exception const & e) 237 | { 238 | std::cerr << "session::start() exception: " << e.what() << std::endl; 239 | } 240 | co_return; 241 | } 242 | 243 | inline 244 | std::size_t id() { return std::hash>{}(shared_from_this()); } 245 | }; 246 | 247 | }// namespace pika 248 | 249 | #endif // SOCKS5_SESSION_HPP_ 250 | --------------------------------------------------------------------------------