├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── Client ├── CMakeLists.txt ├── CliMain.cpp ├── Server.cpp ├── Server.h ├── Session.cpp ├── Session.h ├── main.cpp ├── mainWindow.h ├── mainwindow.cpp └── mainwindow.ui ├── ConfigTemplate ├── client.json ├── overplus.service └── server.json ├── Dockerfile ├── LICENSE ├── Makefile ├── Protocol ├── CMakeLists.txt ├── VProtocal │ ├── VRequest.cpp │ └── VRequest.h ├── socks5 │ ├── socks5.cpp │ └── socks5.h └── trojan │ ├── TrojanReq.cpp │ ├── TrojanReq.h │ ├── UDPPacket.cpp │ └── UDPPacket.h ├── README.md ├── Server ├── CMakeLists.txt ├── Service.cpp ├── Service.h ├── Session.cpp ├── Session.h ├── TlsSession.cpp ├── TlsSession.h ├── WebsocketSession.cpp ├── WebsocketSession.h └── main.cpp ├── Shared ├── CMakeLists.txt ├── Coding.cpp ├── Coding.h ├── ConfigManage.cpp ├── ConfigManage.h ├── IoContextPool.cpp ├── IoContextPool.h ├── Log.cpp ├── Log.h ├── LogFile.cpp ├── LogFile.h └── Version.h ├── asset ├── cert.crt ├── cert.key ├── flow.png ├── flow.wsd └── windows_socks5_proxy.md ├── azure-pipelines.yml ├── buildFileEnv ├── install.sh └── vcpkg.json /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: WebKit 4 | SpaceAfterTemplateKeyword: false 5 | AlignEscapedNewlines: true 6 | AlignTrailingComments: true 7 | BreakBeforeInheritanceComma: true 8 | BreakConstructorInitializers: BeforeComma 9 | IndentPPDirectives: AfterHash 10 | BreakBeforeBraces: Custom 11 | BraceWrapping: 12 | AfterFunction: true 13 | NamespaceIndentation: None -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vcpkg 2 | build/ 3 | .cache 4 | .idea/ 5 | cmake-build-debug/ 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(overplus) 3 | set(CMAKE_CXX_STANDARD 17) 4 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 5 | #set(OPENSSL_USE_STATIC_LIBS TRUE) 6 | #add_definitions(-Wall) 7 | #add_compile_options(-fsanitize=address) 8 | #add_link_options(-fsanitize=address) 9 | add_compile_options(-DBOOST_ASIO_CONCURRENCY_HINT_1=BOOST_ASIO_CONCURRENCY_HINT_UNSAFE_IO) 10 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 11 | #set(Boost_USE_STATIC_LIBS on) 12 | include_directories(.) 13 | find_package(Boost 1.66 COMPONENTS system program_options REQUIRED) 14 | if(Boost_FOUND) 15 | include_directories(${Boost_INCLUDE_DIRS}) 16 | link_directories(${Boost_LIBRARY_DIRS}) 17 | endif() 18 | find_package(Threads REQUIRED) 19 | ###generate server binary code 20 | find_package(OpenSSL REQUIRED) 21 | 22 | 23 | add_subdirectory(Shared) 24 | add_subdirectory(Server) 25 | add_subdirectory(Protocol) 26 | add_subdirectory(Client) 27 | 28 | add_executable(overplus Server/main.cpp) 29 | target_link_libraries(overplus Threads::Threads shared server proto OpenSSL::SSL) 30 | target_link_libraries(overplus ${Boost_LIBRARIES}) 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Client/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | message(STATUS "Enter client CMakeList ...") 2 | if (WIN32) 3 | set(CMAKE_AUTOUIC ON) 4 | set(CMAKE_AUTOMOC ON) 5 | set(CMAKE_AUTORCC ON) 6 | find_package(Qt5Core REQUIRED) 7 | find_package(Qt5Gui REQUIRED) 8 | find_package(Qt5Widgets REQUIRED) 9 | 10 | set(SRCS 11 | Server.h 12 | Server.cpp 13 | Session.cpp 14 | mainwindow.cpp 15 | mainwindow.h 16 | mainwindow.ui 17 | ) 18 | 19 | 20 | add_executable(overplus_client main.cpp ${SRCS}) 21 | target_link_libraries(overplus_client Threads::Threads shared proto OpenSSL::SSL 22 | Qt5::Core Qt5::Gui Qt5::Widgets ${Boost_LIBRARIES} wsock32 ws2_32 crypt32) 23 | set_target_properties(overplus_client PROPERTIES 24 | MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com 25 | MACOSX_BUNDLE TRUE 26 | WIN32_EXECUTABLE TRUE 27 | ) 28 | else () 29 | set(SRCS 30 | Server.h 31 | Server.cpp 32 | Session.cpp 33 | ) 34 | add_executable(overplus_client CliMain.cpp ${SRCS}) 35 | target_link_libraries(overplus_client Threads::Threads shared proto OpenSSL::SSL 36 | ${Boost_LIBRARIES}) 37 | endif () 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Client/CliMain.cpp: -------------------------------------------------------------------------------- 1 | #include "Server.h" 2 | #include "Shared/ConfigManage.h" 3 | //#include 4 | #include 5 | 6 | int main() 7 | { 8 | ConfigManage::instance().load_config("client.json", ConfigManage::Client); 9 | auto& config = ConfigManage::instance().client_cfg; 10 | Server server(config.local_addr, config.local_port); 11 | server.run(); 12 | 13 | return 0; 14 | } -------------------------------------------------------------------------------- /Client/Server.cpp: -------------------------------------------------------------------------------- 1 | #include "Server.h" 2 | #include 3 | #include 4 | #include 5 | 6 | Server::Server(const std::string& address, const std::string& port) 7 | : context_pool(2) 8 | , io_context(context_pool.get_io_context()) 9 | // , signals(io_context) 10 | , acceptor_(io_context) 11 | , ssl_ctx(boost::asio::ssl::context::tlsv13) 12 | { 13 | add_signals(); 14 | ip::tcp::resolver resover(io_context); 15 | local_endpoint = *resover.resolve(address, port).begin(); 16 | 17 | // start_accept(); 18 | } 19 | 20 | void Server::start_accept() 21 | { 22 | acceptor_.open(local_endpoint.protocol()); 23 | acceptor_.set_option(ip::tcp::acceptor::reuse_address(true)); 24 | acceptor_.bind(local_endpoint); 25 | acceptor_.listen(); 26 | do_accept(); 27 | } 28 | void Server::do_accept() 29 | { 30 | std::shared_ptr new_session = std::make_shared(context_pool.get_io_context(), ssl_ctx); 31 | acceptor_.async_accept(new_session->socket(), [this, new_session](const boost::system::error_code& ec) { 32 | if (!acceptor_.is_open()) { 33 | return; 34 | } 35 | if (ec == boost::asio::error::operation_aborted) { 36 | NOTICE_LOG << "got cancel signal, stop calling myself"; 37 | return; 38 | } 39 | if (!ec) { 40 | boost::system::error_code error; 41 | auto ep = new_session->socket().remote_endpoint(error); 42 | NOTICE_LOG << "accept incoming connection :" << ep.address().to_string(); 43 | new_session->start(); 44 | } else { 45 | NOTICE_LOG << "accept incoming connection fail:" << ec.message(); 46 | } 47 | do_accept(); 48 | }); 49 | } 50 | void Server::run() 51 | { 52 | NOTICE_LOG << "Server start..." << std::endl; 53 | context_pool.run(); 54 | } 55 | void Server::stop() 56 | { 57 | NOTICE_LOG << "Server stopped..." << std::endl; 58 | context_pool.stop(); 59 | } 60 | void Server::stop_accept() 61 | { 62 | if(acceptor_.is_open()) 63 | { 64 | acceptor_.cancel(); 65 | acceptor_.close(); 66 | } 67 | } 68 | void Server::add_signals() 69 | { 70 | /* signals.add(SIGINT); 71 | signals.add(SIGTERM); 72 | #ifdef SIGQUIT 73 | signals.add(SIGQUIT); 74 | #endif 75 | signals.async_wait([this](const boost::system::error_code& ec, int sig) { 76 | acceptor_.close(); 77 | 78 | NOTICE_LOG << "Server stopped..." << std::endl; 79 | exit(1); 80 | });*/ 81 | } 82 | -------------------------------------------------------------------------------- /Client/Server.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_H_ 2 | #define SERVER_H_ 3 | #include "Shared/IoContextPool.h" 4 | #include "Shared/Log.h" 5 | #include "Session.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | using namespace boost::asio; 11 | 12 | class Server : private boost::noncopyable { 13 | public: 14 | Server(const std::string& address, const std::string& port); 15 | void run(); 16 | void stop(); 17 | void start_accept(); 18 | void stop_accept(); 19 | 20 | private: 21 | void add_signals(); 22 | void do_accept(); 23 | 24 | 25 | private: 26 | IoContextPool context_pool; 27 | // ios_deque io_contexts_; 28 | boost::asio::io_context& io_context; 29 | 30 | //boost::asio::signal_set signals; 31 | boost::asio::ip::tcp::acceptor acceptor_; 32 | 33 | boost::asio::ssl::context ssl_ctx; 34 | std::shared_ptr new_session; 35 | ip::tcp::endpoint local_endpoint; 36 | }; 37 | #endif 38 | -------------------------------------------------------------------------------- /Client/Session.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Session.h" 3 | #include "Shared/ConfigManage.h" 4 | #include "Shared/Log.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | Session::Session(boost::asio::io_context& context, boost::asio::ssl::context& ssl) 12 | : context_(context) 13 | , in_socket(context_) 14 | 15 | , resolver_(context_) 16 | , in_buf(MAX_BUFF_SIZE) 17 | , out_buf(MAX_BUFF_SIZE) 18 | , ssl_ctx(ssl) 19 | , out_socket(context_, ssl_ctx) 20 | 21 | { 22 | out_socket.set_verify_mode(boost::asio::ssl::verify_none); 23 | auto& config = ConfigManage::instance().client_cfg; 24 | remote_host = config.remote_addr; 25 | remote_port = config.remote_port; 26 | } 27 | 28 | void Session::start() 29 | { 30 | state_ = HANDSHAKE; 31 | sock5_handshake(); 32 | } 33 | 34 | void Session::sock5_handshake() 35 | { 36 | auto self(shared_from_this()); 37 | in_socket.async_read_some(boost::asio::buffer(in_buf), [self, this](const boost::system::error_code& ec, size_t len) { 38 | if (!ec) { 39 | 40 | AuthReq auth_req; 41 | if (auth_req.unstream(in_buf)) { 42 | NOTICE_LOG << "receive message:" << auth_req; 43 | write_sock5_hanshake_reply(auth_req); 44 | 45 | } else { 46 | ERROR_LOG << "Receive invalid message"; 47 | destroy(); 48 | } 49 | 50 | } else { 51 | ERROR_LOG << "sock5 handshake error:" << ec.message(); 52 | destroy(); 53 | } 54 | }); 55 | } 56 | void Session::write_sock5_hanshake_reply(AuthReq& req) 57 | { 58 | auto self(shared_from_this()); 59 | auto it = std::find(req.methods.cbegin(), req.methods.cend(), AuthMethod::NO_AUTHENTICATION); 60 | if (it == req.methods.cend()) { 61 | ERROR_LOG << "Now only support no password auth"; 62 | destroy(); 63 | } 64 | { 65 | AuthRes authRes; 66 | authRes.version = 0x05; 67 | authRes.method = AuthMethod::NO_AUTHENTICATION; 68 | 69 | authRes.stream(message_buf); 70 | } 71 | boost::asio::async_write(in_socket, boost::asio::buffer(message_buf), // Always 2-byte according to RFC1928 72 | [this, self](boost::system::error_code ec, std::size_t length) { 73 | if (!ec) { 74 | if (in_buf[1] == (char)0xFF) 75 | return; // No appropriate auth method found. Close session. 76 | read_socks5_request(); 77 | } else { 78 | ERROR_LOG << "SOCKS5 handshake response write :" << ec.message(); 79 | destroy(); 80 | } 81 | }); 82 | } 83 | void Session::read_socks5_request() 84 | { 85 | auto self(shared_from_this()); 86 | 87 | in_socket.async_read_some(boost::asio::buffer(in_buf), 88 | [this, self](boost::system::error_code ec, std::size_t length) { 89 | if (!ec) { 90 | if (!socks5_req.unsteam(in_buf, length)) { 91 | ERROR_LOG << "decode mesage error"; 92 | destroy(); 93 | return; 94 | } 95 | NOTICE_LOG<<"Receive socks5 message "< " << std::to_string(length) << " bytes"; 212 | 213 | write_packet(1, length); 214 | } else // if (ec != boost::asio::error::eof) 215 | { 216 | ERROR_LOG << "closing session. Client socket read error" << ec.message(); 217 | // Most probably client closed socket. Let's close both sockets and exit session. 218 | destroy(); 219 | // context_.stop(); 220 | } 221 | }); 222 | 223 | if (direction & 0x2) 224 | out_socket.async_read_some(boost::asio::buffer(out_buf), 225 | [this, self](boost::system::error_code ec, std::size_t length) { 226 | if (!ec) { 227 | 228 | DEBUG_LOG << "<-- " << std::to_string(length) << " bytes"; 229 | 230 | write_packet(2, length); 231 | } else // if (ec != boost::asio::error::eof) 232 | { 233 | ERROR_LOG << "closing session. Remote socket read error" << ec.message(); 234 | // Most probably remote server closed socket. Let's close both sockets and exit session. 235 | destroy(); 236 | // context_.stop(); 237 | } 238 | }); 239 | } 240 | void Session::write_packet(int direction, size_t len) 241 | { 242 | auto self(shared_from_this()); 243 | 244 | switch (direction) { 245 | case 1: 246 | boost::asio::async_write(out_socket, boost::asio::buffer(in_buf, len), 247 | [this, self, direction](boost::system::error_code ec, std::size_t length) { 248 | if (!ec) 249 | read_packet(direction); 250 | else { 251 | ERROR_LOG << "closing session. Client socket write error" << ec.message(); 252 | // Most probably client closed socket. Let's close both sockets and exit session. 253 | destroy(); 254 | } 255 | }); 256 | break; 257 | case 2: 258 | boost::asio::async_write(in_socket, boost::asio::buffer(out_buf, len), 259 | [this, self, direction](boost::system::error_code ec, std::size_t length) { 260 | if (!ec) 261 | read_packet(direction); 262 | else { 263 | ERROR_LOG << "closing session. Remote socket write error", ec.message(); 264 | // Most probably remote server closed socket. Let's close both sockets and exit session. 265 | destroy(); 266 | } 267 | }); 268 | break; 269 | } 270 | } 271 | boost::asio::ip::tcp::socket& Session::socket() 272 | { 273 | return in_socket; 274 | } 275 | void Session::destroy() 276 | { 277 | ERROR_LOG << "destroy session"; 278 | // Log::log_with_endpoint(in_endpoint, "disconnected, " + to_string(recv_len) + " bytes received, " + to_string(sent_len) + " bytes sent, lasted for " + to_string(time(nullptr) - start_time) + " seconds", Log::INFO); 279 | boost::system::error_code ec; 280 | resolver_.cancel(); 281 | 282 | if (in_socket.is_open()) { 283 | in_socket.cancel(ec); 284 | in_socket.shutdown(tcp::socket::shutdown_both, ec); 285 | in_socket.close(ec); 286 | } 287 | 288 | if (out_socket.lowest_layer().is_open()) { 289 | auto self = shared_from_this(); 290 | 291 | out_socket.lowest_layer().shutdown(tcp::socket::shutdown_both, ec); 292 | out_socket.lowest_layer().close(); 293 | // ssl_shutdown_timer.expires_after(chrono::seconds(SSL_SHUTDOWN_TIMEOUT)); 294 | // ssl_shutdown_timer.async_wait(ssl_shutdown_cb); 295 | } 296 | } -------------------------------------------------------------------------------- /Client/Session.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "Shared/Log.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | using boost::asio::ip::tcp; 13 | class Session : public std::enable_shared_from_this 14 | , private boost::noncopyable { 15 | enum State { 16 | HANDSHAKE, 17 | FORWARD 18 | }; 19 | 20 | public: 21 | Session(boost::asio::io_context& context, boost::asio::ssl::context& ssl); 22 | 23 | void start(); 24 | void do_read(); 25 | boost::asio::ip::tcp::socket& socket(); 26 | // void handle_sock5(); 27 | void sock5_handshake(); 28 | void read_packet(int); 29 | void write_sock5_hanshake_reply(AuthReq& req); 30 | void read_socks5_request(); 31 | void do_resolve(); 32 | void do_connect(tcp::resolver::iterator&); 33 | void write_socks5_response(); 34 | void write_packet(int, size_t); 35 | void do_sent_v_req(); 36 | void do_ssl_handshake(); 37 | void destroy(); 38 | 39 | private: 40 | static constexpr size_t MAX_BUFF_SIZE = 8192; 41 | boost::asio::io_context& context_; 42 | tcp::socket in_socket; 43 | 44 | // 45 | std::string remote_host; 46 | std::string remote_port; 47 | // 48 | tcp::resolver resolver_; 49 | // 50 | std::vector in_buf; 51 | std::vector out_buf; 52 | std::string message_buf; 53 | Request socks5_req; 54 | State state_ { HANDSHAKE }; 55 | boost::asio::ssl::context& ssl_ctx; 56 | boost::asio::ssl::stream out_socket; 57 | }; -------------------------------------------------------------------------------- /Client/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Server.h" 3 | #include "Shared/ConfigManage.h" 4 | 5 | #include "Shared/Log.h" 6 | #include "Shared/LogFile.h" 7 | #include 8 | 9 | #include "mainwindow.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | const char* LOCAL_PROXY_ADDR = "127.0.0.1"; 16 | const char* LOCAL_PROXY_PORT = "1080"; 17 | const char* LOCAL_PROXY_PROTOCOL = "socks://127.0.0.1:1080"; 18 | bool disable_system_socks_proxy() 19 | { 20 | HKEY hRoot = HKEY_CURRENT_USER; 21 | const char* szSubKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"; 22 | HKEY hKey; 23 | DWORD dwDisposition = REG_OPENED_EXISTING_KEY; 24 | LONG lRet = RegCreateKeyEx(hRoot, szSubKey, 0, NULL, 25 | REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, &dwDisposition); 26 | if (lRet != ERROR_SUCCESS) 27 | return false; 28 | 29 | // disable proxy 30 | int val = 0; 31 | 32 | lRet = RegSetValueEx(hKey, "ProxyEnable", 0, REG_DWORD, (BYTE*)&val, sizeof(DWORD)); 33 | 34 | if (lRet == ERROR_SUCCESS) { 35 | NOTICE_LOG << "disable proxy succussfully!"; 36 | } else { 37 | ERROR_LOG << "disable proxy failed!"; 38 | return false; 39 | } 40 | 41 | RegCloseKey(hKey); 42 | return true; 43 | } 44 | 45 | bool enable_system_socks_proxy() 46 | { 47 | HKEY hRoot = HKEY_CURRENT_USER; 48 | const char* szSubKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"; 49 | HKEY hKey; 50 | DWORD dwDisposition = REG_OPENED_EXISTING_KEY; 51 | LONG lRet = RegCreateKeyEx(hRoot, szSubKey, 0, NULL, 52 | REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, &dwDisposition); 53 | if (lRet != ERROR_SUCCESS) 54 | return false; 55 | lRet = RegSetValueEx(hKey, "ProxyServer", 0, REG_SZ, (BYTE*)LOCAL_PROXY_PROTOCOL, strlen(LOCAL_PROXY_PROTOCOL)); 56 | 57 | if (lRet == ERROR_SUCCESS) { 58 | NOTICE_LOG << "proxy protocol set succussfull"; 59 | } else { 60 | ERROR_LOG << "proxy protocol set failed!"; 61 | return false; 62 | } 63 | 64 | // enable proxy 65 | int val = 1; 66 | 67 | lRet = RegSetValueEx(hKey, "ProxyEnable", 0, REG_DWORD, (BYTE*)&val, sizeof(DWORD)); 68 | 69 | if (lRet == ERROR_SUCCESS) { 70 | NOTICE_LOG << "enable proxy succussfully"; 71 | } else { 72 | ERROR_LOG << "enable proxy failed!"; 73 | return false; 74 | } 75 | 76 | RegCloseKey(hKey); 77 | return true; 78 | } 79 | int main(int argc, char* argv[]) 80 | { 81 | QFileInfo file("client.json"); 82 | auto& config = ConfigManage::instance().client_cfg; 83 | if (file.isFile()) { 84 | ConfigManage::instance().load_config("client.json", ConfigManage::Client); 85 | 86 | } else { 87 | config.local_addr = LOCAL_PROXY_ADDR; 88 | config.local_port = LOCAL_PROXY_PORT; 89 | config.user_name = "test_usr"; 90 | } 91 | LogFile logfile_("overplus", 10 * 1024 * 1024); 92 | // logger::set_log_level(ConfigManage::instance().server_cfg.log_level); 93 | logger::set_log_destination(Destination::D_FILE); 94 | logger::setOutput([&](std::string&& buf) { 95 | logfile_.append(std::move(buf)); 96 | }); 97 | logger::setFlush([&]() { 98 | logfile_.flush(); 99 | }); 100 | if (!enable_system_socks_proxy()) { 101 | return 1; 102 | } 103 | Server server(config.local_addr, config.local_port); 104 | QApplication a(argc, argv); 105 | MainWindow w(server); 106 | w.show(); 107 | std::shared_ptr backgroud(new std::thread([&server] { 108 | server.run(); 109 | })); 110 | 111 | auto rc = a.exec(); 112 | server.stop(); 113 | backgroud->join(); 114 | 115 | disable_system_socks_proxy(); 116 | 117 | return rc; 118 | } 119 | -------------------------------------------------------------------------------- /Client/mainWindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include"Server.h" 6 | 7 | QT_BEGIN_NAMESPACE 8 | namespace Ui { class MainWindow; } 9 | QT_END_NAMESPACE 10 | 11 | class MainWindow : public QMainWindow 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | MainWindow(Server&s,QWidget *parent = nullptr); 17 | ~MainWindow(); 18 | public slots: 19 | void onConnect(); 20 | void onDisconnect(); 21 | void onCheckBoxClick(); 22 | private: 23 | Ui::MainWindow *ui; 24 | Server& server; 25 | }; 26 | #endif // MAINWINDOW_H 27 | -------------------------------------------------------------------------------- /Client/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "./ui_mainwindow.h" 3 | #include 4 | #include "Shared/ConfigManage.h" 5 | 6 | MainWindow::MainWindow(Server&s,QWidget *parent) 7 | : QMainWindow(parent) 8 | , ui(new Ui::MainWindow) 9 | ,server(s) 10 | { 11 | 12 | ui->setupUi(this); 13 | setWindowTitle("overplus"); 14 | connect(ui->CONNECT_BUTTON, SIGNAL(clicked()), this, SLOT(onConnect())); 15 | connect(ui->DISCONNECT_BUTTON, SIGNAL(clicked()), this, SLOT(onDisconnect())); 16 | connect(ui->checkBox, SIGNAL(clicked()), this, SLOT(onCheckBoxClick())); 17 | 18 | if(ConfigManage::instance().loaded) 19 | { auto& config = ConfigManage::instance().client_cfg; 20 | ui->HOST_NAME->setText(QString::fromStdString(config.remote_addr)); 21 | ui->HOST_PORT->setText(QString::fromStdString(config.remote_port)); 22 | ui->HOST_PASSWD->setText(QString::fromStdString(config.text_password)); 23 | } 24 | 25 | 26 | } 27 | 28 | MainWindow::~MainWindow() 29 | { 30 | 31 | delete ui; 32 | 33 | } 34 | void MainWindow::onConnect() 35 | { 36 | 37 | ui->CONNECT_BUTTON->setEnabled(false); 38 | ui->DISCONNECT_BUTTON->setEnabled(true); 39 | ui->CONNECTION_STATUS->setText("CONNECTED"); 40 | 41 | auto& config = ConfigManage::instance().client_cfg; 42 | config.remote_addr = ui->HOST_NAME->text().toStdString(); 43 | config.remote_port = ui->HOST_PORT->text().toStdString(); 44 | 45 | auto psswd = ui->HOST_PASSWD->text().toStdString(); 46 | config.setPassword(psswd); 47 | NOTICE_LOG<<"Read config frome user input:"< 50 | server.start_accept(); 51 | 52 | 53 | 54 | } 55 | 56 | void MainWindow::onDisconnect() 57 | { 58 | server.stop_accept(); 59 | ui->CONNECT_BUTTON->setEnabled(true); 60 | ui->DISCONNECT_BUTTON->setEnabled(false); 61 | ui->CONNECTION_STATUS->setText("DISCONNECTED"); 62 | 63 | } 64 | void MainWindow::onCheckBoxClick(){ 65 | 66 | ui->HOST_PASSWD->setEchoMode(ui->checkBox->checkState() == Qt::Checked ? QLineEdit::Normal : QLineEdit::Password ); 67 | } 68 | 69 | 70 | -------------------------------------------------------------------------------- /Client/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 648 10 | 800 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 30 21 | 40 22 | 91 23 | 171 24 | 25 | 26 | 27 | 28 | 29 | 30 | Host 31 | 32 | 33 | 34 | 35 | 36 | 37 | Port 38 | 39 | 40 | 41 | 42 | 43 | 44 | Password 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 172 54 | 40 55 | 281 56 | 171 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | QLineEdit::Password 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 40 79 | 649 80 | 431 81 | 31 82 | 83 | 84 | 85 | 86 | 87 | 88 | Connect 89 | 90 | 91 | 92 | 93 | 94 | 95 | Disconnect 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 30 105 | 251 106 | 101 107 | 111 108 | 109 | 110 | 111 | 112 | 113 | 114 | Connection status 115 | 116 | 117 | 118 | 119 | 120 | 121 | Bytes In 122 | 123 | 124 | 125 | 126 | 127 | 128 | Bytes out 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 160 138 | 251 139 | 261 140 | 111 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | true 149 | 150 | 151 | 152 | DISCONNECTED 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | true 161 | 162 | 163 | 164 | N/A 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | true 173 | 174 | 175 | 176 | N/A 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 460 186 | 170 187 | 91 188 | 20 189 | 190 | 191 | 192 | visible 193 | 194 | 195 | 196 | 197 | 198 | 199 | 0 200 | 0 201 | 648 202 | 17 203 | 204 | 205 | 206 | 207 | 208 | 209 | HOST_NAME 210 | HOST_PORT 211 | CONNECT_BUTTON 212 | DISCONNECT_BUTTON 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /ConfigTemplate/client.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_type": "client", 3 | "local_addr": "localhost", 4 | "local_port": "1080", 5 | "remote_addr": "1.2.3.4", 6 | "remote_port": "443", 7 | "user_name":"user1", 8 | "password": "password1" 9 | } 10 | -------------------------------------------------------------------------------- /ConfigTemplate/overplus.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=overplus - An proxy tool helps you bypass GFW 3 | After=network.target 4 | [Service] 5 | User=root 6 | ExecStart=/usr/bin/overplus -c /etc/overplus/server.json 7 | Restart=on-failure 8 | RestartSec=10s 9 | LimitNOFILE=infinity 10 | 11 | [Install] 12 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /ConfigTemplate/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_type": "server", 3 | "local_addr": "0.0.0.0", 4 | "local_port": "VAR_PORT", 5 | "allowed_passwords": [ 6 | "VAR_PASSWORD" 7 | ], 8 | "log_level": "NOTICE", 9 | "log_dir":"", 10 | "ssl": { 11 | "cert": "VAR_SERVER_CERT", 12 | "key": "VAR_SERVER_KEY" 13 | }, 14 | "websocketEnabled": false 15 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | LABEL Description="overplus docker image" 3 | ENV HOME /root 4 | 5 | SHELL ["/bin/bash", "-c"] 6 | 7 | COPY build/overplus /usr/local/bin/overplus 8 | 9 | RUN apt-get update && apt-get -y --no-install-recommends install \ 10 | build-essential \ 11 | libssl-dev \ 12 | wget 13 | 14 | # Let us add some heavy dependency 15 | RUN cd ${HOME} && \ 16 | wget --no-check-certificate --quiet \ 17 | https://boostorg.jfrog.io/artifactory/main/release/1.66.0/source/boost_1_66_0.tar.gz && \ 18 | tar xzf ./boost_1_66_0.tar.gz && \ 19 | cd ./boost_1_66_0 && \ 20 | ./bootstrap.sh && \ 21 | ./b2 install && \ 22 | cd .. && \ 23 | rm -rf ./boost_1_66 24 | 25 | RUN mkdir -p /etc/overplus 26 | 27 | COPY ConfigTemplate/server.json /etc/overplus/server.json 28 | COPY ConfigTemplate/cert.crt /etc/overplus/cert.crt 29 | COPY ConfigTemplate/cert.key /etc/overplus/cert.key 30 | 31 | 32 | 33 | #WORKDIR /config 34 | CMD ["overplus","/etc/overplus/server.json"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2022, Yanrong 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | DOCKER_DEPS_IMAGE_BUILD_FLAGS?=--no-cache=true 3 | PWD := $(shell pwd) 4 | 5 | gen_cmake: 6 | docker run -it --init --rm --memory-swap=-1 --ulimit core=-1 --name="overplus" \ 7 | --workdir=/srv \ 8 | --mount type=bind,source=${PWD},target=/src \ 9 | yanrongdocker/overplus_build_base:latest \ 10 | bash -c \ 11 | "mkdir -p /src/build && \ 12 | cd /src/build && \ 13 | cmake .." 14 | 15 | build: gen_cmake 16 | docker run -it --init --rm --memory-swap=-1 --ulimit core=-1 --name="overplus" \ 17 | --workdir=/src \ 18 | --mount type=bind,source="${PWD}",target=/src \ 19 | yanrongdocker/overplus_build_base:latest \ 20 | bash -c \ 21 | "cd build && \ 22 | make " 23 | build_image: 24 | docker build ${DOCKER_DEPS_IMAGE_BUILD_FLAGS} -t yanrongdocker/overplus_docker:latest\ 25 | -f ./Dockerfile . 26 | 27 | build-docker-deps-image: 28 | docker build ${DOCKER_DEPS_IMAGE_BUILD_FLAGS} -t yanrongdocker/overplus_build_base:latest \ 29 | -f ./buildFileEnv . -------------------------------------------------------------------------------- /Protocol/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | message(STATUS "Enter Protocal CMakeList ...") 2 | set(proto_SRCS 3 | socks5/socks5.cpp 4 | trojan/TrojanReq.cpp 5 | trojan/UDPPacket.cpp 6 | VProtocal/VRequest.cpp 7 | ) 8 | 9 | add_library(proto ${proto_SRCS}) 10 | target_link_libraries(proto shared) 11 | -------------------------------------------------------------------------------- /Protocol/VProtocal/VRequest.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "VRequest.h" 3 | #include "Shared/Coding.h" 4 | #include "Shared/Log.h" 5 | #include 6 | 7 | #ifdef _WIN32 8 | # include 9 | #else 10 | 11 | # include 12 | 13 | #endif 14 | 15 | void VRequest::stream(std::string &buf) { 16 | 17 | header.len = 18 | 5 + identifier_len + password.length() + 4 + user_name.length() + 4 + address.length() + 4 + sizeof(port); 19 | Coding::EncodeCstr(buf, v_identifier); 20 | Coding::EncodeFixed8(buf, header.version); 21 | Coding::EncodeFixed32(buf, htonl(header.len)); 22 | 23 | // 24 | Coding::EncodeFixed32(buf, htonl(user_name.length())); 25 | Coding::EncodeStr(buf, user_name); 26 | Coding::EncodeFixed32(buf, htonl(password.length())); 27 | Coding::EncodeStr(buf, password); 28 | // 29 | Coding::EncodeFixed32(buf, htonl(address.length())); 30 | Coding::EncodeStr(buf, address); 31 | // 32 | Coding::EncodeFixed16(buf, htons(port)); 33 | DEBUG_LOG << "header.len=" << header.len << " dump buf:" << buf; 34 | } 35 | 36 | bool VRequest::unstream(const std::string &buf) { 37 | if (buf.length() < sizeof(Header)) { 38 | NOTICE_LOG << "Buf len:" << buf.length() << " head size:" << sizeof(Header); 39 | return false; 40 | } 41 | // int pos = 0; 42 | char *ptr = const_cast(buf.data()); 43 | // skip identifier 44 | ptr += identifier_len; 45 | this->header.version = *ptr; 46 | ptr++; 47 | this->header.len = ntohl(Coding::DecodeFixed32(ptr)); 48 | if (this->header.len > buf.length()) { 49 | NOTICE_LOG << "header.len:" << this->header.len << " buf length:" << buf.length(); 50 | return false; 51 | } 52 | ptr += 4; 53 | uint32_t len = ntohl(Coding::DecodeFixed32(ptr)); 54 | ptr += 4; 55 | this->user_name = Coding::DecodeStr(ptr, len); 56 | ptr += len; 57 | len = ntohl(Coding::DecodeFixed32(ptr)); 58 | ptr += 4; 59 | this->password = Coding::DecodeStr(ptr, len); 60 | ptr += len; 61 | len = ntohl(Coding::DecodeFixed32(ptr)); 62 | ptr += 4; 63 | this->address = Coding::DecodeStr(ptr, len); 64 | ptr += len; 65 | this->port = ntohs(Coding::DecodeFixed16(ptr)); 66 | if (buf.size() > header.len) { 67 | packed_buff.assign(ptr + 2, buf.size() - header.len); 68 | } 69 | return true; 70 | } 71 | 72 | bool VRequest::is_v_protocol(std::vector &buf) { 73 | DEBUG_LOG << "Incoming identifier:" << std::string(buf.data(), identifier_len); 74 | return memcmp(v_identifier, buf.data(), identifier_len) == 0; 75 | } 76 | -------------------------------------------------------------------------------- /Protocol/VProtocal/VRequest.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "Shared/Log.h" 8 | class VRequest { 9 | public: 10 | static constexpr const char* v_identifier = "v protocol"; 11 | static constexpr int identifier_len = 10; 12 | public: 13 | void stream(std::string& buf); 14 | bool unstream(const std::string& buf); 15 | static bool is_v_protocol(std::vector&buf); 16 | 17 | 18 | public: 19 | struct Header { 20 | std::string identifier; 21 | uint8_t version; 22 | std::uint32_t len; 23 | }; 24 | Header header; 25 | std::string user_name; 26 | std::string password; 27 | // 28 | std::string address; 29 | uint16_t port; 30 | 31 | //packed buffer, Don't stream/unstream this field 32 | std::string packed_buff; 33 | }; -------------------------------------------------------------------------------- /Protocol/socks5/socks5.cpp: -------------------------------------------------------------------------------- 1 | #include "socks5.h" 2 | 3 | std::string method_to_string(AuthMethod method) 4 | { 5 | switch (method) { 6 | case NO_AUTHENTICATION: 7 | return "NO_AUTHENTICATION"; 8 | case GSSAPI: 9 | return "GSSAPI"; 10 | case PASSWORD: 11 | return "PASSWORD"; 12 | case IANA_ASSIGNED: 13 | return "IANA_ASSIGNED"; 14 | case RESERVED: 15 | return "RESERVED"; // 0X80-0XFE 16 | case NO_ACCEPTABLE_METHODS: 17 | return "NO_ACCEPTABLE_METHODS"; 18 | } 19 | return ""; 20 | } 21 | std::ostream& operator<<(std::ostream& os, AuthReq& req) 22 | { 23 | os << "AuthReq {VER:" << (int)req.version << ", methods: ["; 24 | for (int i = 0; i < req.nmethod; i++) { 25 | os << method_to_string(req.methods[i]) << " "; 26 | } 27 | os << "]}"; 28 | return os; 29 | } 30 | 31 | bool AuthReq::unstream(std::vector& buf) 32 | { 33 | 34 | char* ptr = buf.data(); 35 | 36 | version = Coding::DecodeFixed8(ptr); 37 | 38 | ptr++; 39 | if (version != (char)0x05) { 40 | ERROR_LOG << "Invalid version number:" << version; 41 | return false; 42 | } 43 | nmethod = Coding::DecodeFixed8(ptr); 44 | ptr++; 45 | for (int i = 0; i < nmethod; ++i) { 46 | AuthMethod method; 47 | auto method_oct = Coding::DecodeFixed8(ptr); 48 | if (i < nmethod - 1) { 49 | ptr++; 50 | } 51 | if (method_oct <= 0x02) { 52 | method = static_cast(method_oct); 53 | } else if (method_oct <= 0x7e) { 54 | method = IANA_ASSIGNED; 55 | } else if (method_oct <= 0xfe) { 56 | method = RESERVED; 57 | } else { 58 | method = NO_ACCEPTABLE_METHODS; 59 | } 60 | methods.push_back(method); 61 | } 62 | return true; 63 | } 64 | 65 | bool Request::unsteam(std::vector& buf, int length) 66 | { 67 | 68 | char* ptr = buf.data(); 69 | version = Coding::DecodeFixed8(ptr); 70 | if (version != (char)0x05) { 71 | ERROR_LOG << "Invalid version number:" << (int)version; 72 | return false; 73 | } 74 | ptr++; 75 | cmd = static_cast(Coding::DecodeFixed8(ptr)); 76 | ptr++; 77 | reserved = Coding::DecodeFixed8(ptr); 78 | if (reserved != (char)0x00) { 79 | ERROR_LOG << "Invalid reserved value:" << (int)reserved; 80 | return false; 81 | } 82 | ptr++; 83 | auto val = Coding::DecodeFixed8(ptr); 84 | if (val == 0x01) { 85 | addrType = V4; 86 | } else if (val == 0x03) { 87 | addrType = DOMAINNAME; 88 | } else if (val == 0x04) { 89 | addrType = V6; 90 | } else { 91 | ERROR_LOG << "Invalid address type:" << (int)val; 92 | return false; 93 | } 94 | ptr++; 95 | switch (addrType) { 96 | case V4: // IP V4 addres 97 | if (length != (size_t)(4 + 4 + 2)) { 98 | ERROR_LOG << "SOCKS5 request(V4) length is invalid. Closing session."; 99 | return false; 100 | } 101 | remote_host = boost::asio::ip::address_v4(ntohl(Coding::DecodeFixed32(ptr))).to_string(); 102 | ptr = ptr + 4; 103 | remote_port = ntohs(Coding::DecodeFixed16(ptr)); 104 | break; 105 | case DOMAINNAME: // DOMAINNAME 106 | { 107 | auto host_length = Coding::DecodeFixed8(ptr); 108 | ptr++; 109 | if (length != (size_t)(5 + host_length + 2)) { 110 | ERROR_LOG << "SOCKS5(domain name) request length is invalid. Closing session." << length; 111 | return false; 112 | } 113 | remote_host = std::string(ptr, host_length); 114 | ptr = ptr + host_length; 115 | remote_port = ntohs(Coding::DecodeFixed16(ptr)); 116 | } break; 117 | default: 118 | ERROR_LOG << "unsupport_ed address type in SOCKS5 request. Closing session."; 119 | return false; 120 | } 121 | return true; 122 | } 123 | std::string Request::command_to_string(CMD cmd) 124 | { 125 | if (cmd == CONNECT) 126 | return "CONNECT"; 127 | if (cmd == BIND) 128 | return "BIND"; 129 | if (cmd == UDP_ASSOCIATE) 130 | return "UDP_ASSOCIATE"; 131 | return "ERR"; 132 | } 133 | std::ostream& operator<<(std::ostream& os, Request& req) 134 | { 135 | os << "Request {VER:" << (int)req.version << ", command:" << Request::command_to_string(req.cmd) 136 | << ", remote_host: " << req.remote_host << ", remote_port:" << req.remote_port << " }"; 137 | return os; 138 | } -------------------------------------------------------------------------------- /Protocol/socks5/socks5.h: -------------------------------------------------------------------------------- 1 | // 2 | // socks5 specification 3 | // https://www.rfc-editor.org/rfc/rfc1928 4 | 5 | #pragma once 6 | #include "Shared/Coding.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | enum AuthMethod : uint8_t { 16 | NO_AUTHENTICATION, // 0X00 17 | GSSAPI, // 0X01 18 | PASSWORD, // 0X02 19 | IANA_ASSIGNED, // 0X03-0X7E 20 | RESERVED, // 0X80-0XFE 21 | NO_ACCEPTABLE_METHODS // 0XFF 22 | 23 | }; 24 | std::string method_to_string(AuthMethod method); 25 | 26 | class AuthReq { 27 | public: 28 | void stream(std::string& buf) 29 | { 30 | } 31 | bool unstream(std::vector& buf); 32 | 33 | public: 34 | uint8_t version; 35 | uint8_t nmethod; 36 | std::vector methods; 37 | }; 38 | std::ostream& operator<<(std::ostream& os, AuthReq& req); 39 | 40 | class AuthRes { 41 | public: 42 | void stream(std::string& buf) 43 | { 44 | Coding::EncodeFixed8(buf, version); 45 | Coding::EncodeFixed8(buf, method); 46 | } 47 | 48 | public: 49 | uint8_t version; 50 | AuthMethod method; 51 | }; 52 | /* +----+-----+-------+------+----------+----------+ 53 | |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 54 | +----+-----+-------+------+----------+----------+ 55 | | 1 | 1 | X'00' | 1 | Variable | 2 | 56 | +----+-----+-------+------+----------+----------+ 57 | */ 58 | enum ADDRTYPE : uint8_t { 59 | V4 = 0x01, 60 | DOMAINNAME = 0x03, 61 | V6 = 0x04 62 | }; 63 | class Request { 64 | public: 65 | enum CMD : uint8_t { 66 | CONNECT = 0x01, 67 | BIND = 0x02, 68 | UDP_ASSOCIATE = 0x03 69 | 70 | }; 71 | 72 | static std::string command_to_string(CMD cmd); 73 | 74 | public: 75 | bool unsteam(std::vector& buf, int length); 76 | 77 | public: 78 | uint8_t version; 79 | CMD cmd; 80 | uint8_t reserved; 81 | uint8_t addrType; 82 | std::string remote_host; 83 | uint16_t remote_port; 84 | }; 85 | std::ostream& operator<<(std::ostream& os, Request& req); 86 | 87 | class Reply { 88 | public: 89 | void stream(std::string& buf) 90 | { 91 | Coding::EncodeFixed8(buf, version); 92 | Coding::EncodeFixed8(buf, repResult); 93 | Coding::EncodeFixed8(buf, reserved); 94 | 95 | Coding::EncodeFixed8(buf, static_cast(addrtype)); 96 | Coding::EncodeFixed32(buf, htonl(realRemoteIP)); 97 | Coding::EncodeFixed16(buf, htons(realRemotePort)); 98 | } 99 | 100 | public: 101 | uint8_t version; 102 | uint8_t repResult; 103 | uint8_t reserved; 104 | ADDRTYPE addrtype; 105 | uint32_t realRemoteIP; 106 | uint16_t realRemotePort; 107 | }; 108 | -------------------------------------------------------------------------------- /Protocol/trojan/TrojanReq.cpp: -------------------------------------------------------------------------------- 1 | #include "TrojanReq.h" 2 | #include "Shared/Log.h" 3 | using namespace std; 4 | bool SOCKS5Address::parse(const string& data, size_t& address_len) 5 | { 6 | if (data.length() == 0 || (data[0] != IPv4 && data[0] != DOMAINNAME && data[0] != IPv6)) { 7 | return false; 8 | } 9 | address_type = static_cast(data[0]); 10 | switch (address_type) { 11 | case IPv4: { 12 | if (data.length() > 4 + 2) { 13 | address = to_string(uint8_t(data[1])) + '.' + to_string(uint8_t(data[2])) + '.' + to_string(uint8_t(data[3])) + '.' + to_string(uint8_t(data[4])); 14 | port = (uint8_t(data[5]) << 8) | uint8_t(data[6]); 15 | address_len = 1 + 4 + 2; 16 | return true; 17 | } 18 | break; 19 | } 20 | case DOMAINNAME: { 21 | uint8_t domain_len = data[1]; 22 | if (domain_len == 0) { 23 | // invalid domain len 24 | break; 25 | } 26 | if (data.length() > (unsigned int)(1 + domain_len + 2)) { 27 | address = data.substr(2, domain_len); 28 | port = (uint8_t(data[domain_len + 2]) << 8) | uint8_t(data[domain_len + 3]); 29 | address_len = 1 + 1 + domain_len + 2; 30 | return true; 31 | } 32 | break; 33 | } 34 | case IPv6: { 35 | if (data.length() > 16 + 2) { 36 | char t[40]; 37 | sprintf(t, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", 38 | uint8_t(data[1]), uint8_t(data[2]), uint8_t(data[3]), uint8_t(data[4]), 39 | uint8_t(data[5]), uint8_t(data[6]), uint8_t(data[7]), uint8_t(data[8]), 40 | uint8_t(data[9]), uint8_t(data[10]), uint8_t(data[11]), uint8_t(data[12]), 41 | uint8_t(data[13]), uint8_t(data[14]), uint8_t(data[15]), uint8_t(data[16])); 42 | address = t; 43 | port = (uint8_t(data[17]) << 8) | uint8_t(data[18]); 44 | address_len = 1 + 16 + 2; 45 | return true; 46 | } 47 | break; 48 | } 49 | } 50 | return false; 51 | } 52 | 53 | string SOCKS5Address::generate(const boost::asio::ip::udp::endpoint& endpoint) 54 | { 55 | if (endpoint.address().is_unspecified()) { 56 | return string("\x01\x00\x00\x00\x00\x00\x00", 7); 57 | } 58 | string ret; 59 | if (endpoint.address().is_v4()) { 60 | ret += '\x01'; 61 | auto ip = endpoint.address().to_v4().to_bytes(); 62 | for (int i = 0; i < 4; ++i) { 63 | ret += char(ip[i]); 64 | } 65 | } 66 | if (endpoint.address().is_v6()) { 67 | ret += '\x04'; 68 | auto ip = endpoint.address().to_v6().to_bytes(); 69 | for (int i = 0; i < 16; ++i) { 70 | ret += char(ip[i]); 71 | } 72 | } 73 | ret += char(uint8_t(endpoint.port() >> 8)); 74 | ret += char(uint8_t(endpoint.port() & 0xFF)); 75 | return ret; 76 | } 77 | int TrojanReq::parse(const string& data) 78 | { 79 | size_t first = data.find("\r\n"); 80 | if (first == string::npos) { 81 | return -1; 82 | } 83 | password = data.substr(0, first); 84 | payload = data.substr(first + 2); 85 | if (payload.length() == 0 || (payload[0] != CONNECT && payload[0] != UDP_ASSOCIATE)) { 86 | return -1; 87 | } 88 | command = static_cast(payload[0]); 89 | size_t address_len; 90 | bool is_addr_valid = address.parse(payload.substr(1), address_len); 91 | if (!is_addr_valid || payload.length() < address_len + 3 || payload.substr(address_len + 1, 2) != "\r\n") { 92 | return -1; 93 | } 94 | payload = payload.substr(address_len + 3); 95 | return data.length(); 96 | } 97 | 98 | string TrojanReq::generate(const string& password, const string& domainname, uint16_t port, bool tcp) 99 | { 100 | string ret = password + "\r\n"; 101 | if (tcp) { 102 | ret += '\x01'; 103 | } else { 104 | ret += '\x03'; 105 | } 106 | ret += '\x03'; 107 | ret += char(uint8_t(domainname.length())); 108 | ret += domainname; 109 | ret += char(uint8_t(port >> 8)); 110 | ret += char(uint8_t(port & 0xFF)); 111 | ret += "\r\n"; 112 | return ret; 113 | } -------------------------------------------------------------------------------- /Protocol/trojan/TrojanReq.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | class SOCKS5Address { 5 | public: 6 | enum AddressType { 7 | IPv4 = 1, 8 | DOMAINNAME = 3, 9 | IPv6 = 4 10 | } address_type; 11 | std::string address; 12 | uint16_t port; 13 | bool parse(const std::string& data, size_t& address_len); 14 | static std::string generate(const boost::asio::ip::udp::endpoint& endpoint); 15 | }; 16 | class TrojanReq { 17 | public: 18 | enum Command { 19 | CONNECT = 1, 20 | UDP_ASSOCIATE = 3 21 | } command; 22 | 23 | // 24 | std::string password; 25 | SOCKS5Address address; 26 | std::string payload; 27 | int parse(const std::string& data); 28 | static std::string generate(const std::string& password, const std::string& domainname, uint16_t port, bool tcp); 29 | }; -------------------------------------------------------------------------------- /Protocol/trojan/UDPPacket.cpp: -------------------------------------------------------------------------------- 1 | #include "UDPPacket.h" 2 | using namespace std; 3 | using namespace boost::asio::ip; 4 | 5 | bool UDPPacket::parse(const string &data, size_t &udp_packet_len) { 6 | if (data.length() <= 0) { 7 | return false; 8 | } 9 | size_t address_len; 10 | bool is_addr_valid = address.parse(data, address_len); 11 | if (!is_addr_valid || data.length() < address_len + 2) { 12 | return false; 13 | } 14 | length = (uint8_t(data[address_len]) << 8) | uint8_t(data[address_len + 1]); 15 | if (data.length() < address_len + 4 + length || data.substr(address_len + 2, 2) != "\r\n") { 16 | return false; 17 | } 18 | payload = data.substr(address_len + 4, length); 19 | udp_packet_len = address_len + 4 + length; 20 | return true; 21 | } 22 | 23 | string UDPPacket::generate(const udp::endpoint &endpoint, const string &payload) { 24 | string ret = SOCKS5Address::generate(endpoint); 25 | ret += char(uint8_t(payload.length() >> 8)); 26 | ret += char(uint8_t(payload.length() & 0xFF)); 27 | ret += "\r\n"; 28 | ret += payload; 29 | return ret; 30 | } 31 | 32 | string UDPPacket::generate(const string &domainname, uint16_t port, const string &payload) { 33 | string ret = "\x03"; 34 | ret += char(uint8_t(domainname.length())); 35 | ret += domainname; 36 | ret += char(uint8_t(port >> 8)); 37 | ret += char(uint8_t(port & 0xFF)); 38 | ret += char(uint8_t(payload.length() >> 8)); 39 | ret += char(uint8_t(payload.length() & 0xFF)); 40 | ret += "\r\n"; 41 | ret += payload; 42 | return ret; 43 | } 44 | -------------------------------------------------------------------------------- /Protocol/trojan/UDPPacket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Protocol/trojan/TrojanReq.h" 3 | class UDPPacket { 4 | public: 5 | UDPPacket(){} 6 | SOCKS5Address address; 7 | uint16_t length; 8 | std::string payload; 9 | bool parse(const std::string &data, size_t &udp_packet_len); 10 | static std::string generate(const boost::asio::ip::udp::endpoint &endpoint, const std::string &payload); 11 | static std::string generate(const std::string &domainname, uint16_t port, const std::string &payload); 12 | }; 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # overplus 2 | Overplus is another implementation of trojan protocol with better perfermance and stability. 3 | 4 | [![Azure DevOps Status](https://dev.azure.com/overplusProxy/overplus/_apis/build/status/xyanrch.overplus?branchName=master)](https://dev.azure.com/overplusProxy/overplus/_build/latest?definitionId=3&branchName=master) 5 | 6 | **Compared with trojan**: 7 | - [x] Lower latency and higher speed 8 | - [x] Enhanced Security 9 | - [x] Easy to deploy 10 | - [x] More readable and cleaner code. 11 | 12 | ## overplus telegram group 13 | 14 | https://t.me/+JfKOqh2wH25kMWFl 15 | 16 | 17 | ## One-click deployment 18 | Run the script and follow the assistant: 19 | 20 | ``` commandline 21 | curl -O https://raw.githubusercontent.com/xyanrch/overplus/master/install.sh && chmod +x install.sh && sudo ./install.sh 22 | ``` 23 | 24 | **It is strongly recommended to enable BBR to accelerate the network speed.** 25 | 26 | **[Enable BBR on 20.04 and CentOS 8](https://cloud.tencent.com/developer/article/1946062)** 27 | ## Client side 28 | A windows client that support v protocol can find in release page. Overplus fully support trojan protocol, so you can use any client which support trojan protocol. Please make sure disable certificate verification if you use a self issued certificate. 29 | 30 | 31 | ## Build 32 | The project depend on boost and openssl libraries, please make sure install boost and openssl before build the project. 33 | 34 | ### How to build 35 | ``` commandline 36 | mkdir build && cd build 37 | cmake .. 38 | make 39 | 40 | ``` 41 | ### How to run 42 | 43 | ``` commandline 44 | sudo ./overplus -c server.json 45 | ``` 46 | server.json is a config file which you can customize. 47 | 48 | #### Example config file 49 | ```json 50 | { 51 | "run_type": "server", 52 | "local_addr": "0.0.0.0", 53 | "local_port": "443", 54 | "allowed_passwords": [ 55 | "testpsswd" 56 | ], 57 | "log_level": "NOTICE", 58 | "log_dir":"", 59 | "ssl": { 60 | "cert": "path_to_cert", 61 | "key": "path_to_key" 62 | }, 63 | "websocketEnabled": false 64 | } 65 | ``` 66 | ## Windows platform build 67 | Windows use vcpkg to manage library 68 | 69 | **Dowload vcpkg** 70 | ```commandline 71 | git clone https://github.com/Microsoft/vcpkg.git 72 | .\vcpkg\bootstrap-vcpkg.bat 73 | ``` 74 | **Install dependence** 75 | ```commandline 76 | .\vcpkg\vcpkg.exe install --triplet x64-windows 77 | ``` 78 | **Build project** 79 | ```commandline 80 | cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE="..\vcpkg\scripts\buildsystems\vcpkg.cmake" 81 | cmake --build build 82 | ``` 83 | ## How does overplus work? 84 | Trojan protocol is a socks5 like protocol.Trojan request is formed as follows: 85 | 86 | +----+-----+-------+------+----------+----------+-------+ 87 | |Command | password| DST.ADDR | DST.PORT |packed payload 88 | +----+-----+-------+------+----------+----------+-------+ 89 | | unit8 | string | string | string | string 90 | +----+-----+-------+------+----------+----------+-------+ 91 | ![flow chart](asset/flow.png) 92 | 93 | ## Roadmap 94 | - [x] Support UDP proxy for trojan protocol 95 | - [x] Support websocket for trojan protocol 96 | - [ ] Implement a web console to manage overplus 97 | - [ ] Design a new protocol to replace trojan protocol 98 | 99 | ## Stargazers over time 100 | 101 | [![Stargazers over time](https://starchart.cc/xyanrch/overplus.svg)](https://starchart.cc/xyanrch/overplus) 102 | 103 | -------------------------------------------------------------------------------- /Server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | message(STATUS "Enter Server CMakeList ...") 2 | 3 | set(SRCS 4 | TlsSession.cpp 5 | WebsocketSession.cpp 6 | Service.cpp 7 | ) 8 | add_library(server ${SRCS}) 9 | target_link_libraries(server shared proto) -------------------------------------------------------------------------------- /Server/Service.cpp: -------------------------------------------------------------------------------- 1 | #include "Service.h" 2 | #include "Server/TlsSession.h" 3 | #include "Shared/Log.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | //#include 9 | #include 10 | #include 11 | 12 | Service::Service() 13 | : context_pool(5), io_context(context_pool.get_io_context()), signals(io_context), acceptor_(io_context), 14 | ssl_context_(boost::asio::ssl::context::sslv23) { 15 | 16 | auto &config_manage = ConfigManage::instance(); 17 | add_signals(); 18 | ip::tcp::resolver resover(io_context); 19 | ip::tcp::endpoint endpoint = *resover.resolve(config_manage.server_cfg.local_addr, 20 | config_manage.server_cfg.local_port).begin(); 21 | load_server_certificate(ssl_context_); 22 | acceptor_.open(endpoint.protocol()); 23 | acceptor_.set_option(tcp::acceptor::reuse_address(true)); 24 | acceptor_.bind(endpoint); 25 | acceptor_.listen(); 26 | if(config_manage.server_cfg.websocketEnabled) 27 | { 28 | NOTICE_LOG<<"listen websocket connection"; 29 | do_websocket_accept(); 30 | } 31 | else 32 | { 33 | do_accept(); 34 | } 35 | 36 | } 37 | void Service::do_websocket_accept() 38 | { 39 | websocket_connection_.reset(new WebsocketSession(context_pool.get_io_context(), ssl_context_)); 40 | acceptor_.async_accept(websocket_connection_->socket(), [this](const boost::system::error_code &ec) { 41 | if(!ec){ 42 | auto ep = websocket_connection_->socket().remote_endpoint(); 43 | NOTICE_LOG << "accept incoming connection " << ep.address().to_string()<<":"<start(); 45 | } 46 | else 47 | { 48 | NOTICE_LOG << "accept incoming connection fail:" << ec.message() << std::endl; 49 | 50 | } 51 | do_websocket_accept(); 52 | 53 | }); 54 | } 55 | 56 | void Service::load_server_certificate(boost::asio::ssl::context &ctx) { 57 | 58 | auto &config_manage = ConfigManage::instance(); 59 | 60 | ctx.set_options( 61 | boost::asio::ssl::context::default_workarounds 62 | | boost::asio::ssl::context::no_sslv2); 63 | // | boost::asio::ssl::context::single_dh_use); 64 | 65 | ssl_context_.use_certificate_chain_file(config_manage.server_cfg.certificate_chain); 66 | ssl_context_.use_private_key_file(config_manage.server_cfg.server_private_key, boost::asio::ssl::context::pem); 67 | } 68 | 69 | 70 | void Service::do_accept() { 71 | new_connection_.reset(new TlsSession(context_pool.get_io_context(), ssl_context_)); 72 | acceptor_.async_accept(new_connection_->socket(), [this](const boost::system::error_code &ec) { 73 | auto clean_up = [this]() { 74 | if (new_connection_->socket().is_open()) { 75 | new_connection_->socket().close(); 76 | } 77 | }; 78 | if (!acceptor_.is_open()) { 79 | NOTICE_LOG << "Acceptor socket is not open, stop calling myself"; 80 | clean_up(); 81 | return; 82 | } 83 | if (ec == boost::asio::error::operation_aborted) { 84 | NOTICE_LOG << "got cancel signal, stop calling myself"; 85 | clean_up(); 86 | return; 87 | } 88 | if (!ec) { 89 | boost::system::error_code error; 90 | auto ep = new_connection_->socket().remote_endpoint(error); 91 | if (!error) { 92 | DEBUG_LOG << "accept incoming connection "<< ep.address().to_string()<<":"<socket().set_option(boost::asio::socket_base::keep_alive(true)); 94 | new_connection_->start(); 95 | 96 | } else { 97 | NOTICE_LOG << "get remote point error :" << error.message(); 98 | clean_up(); 99 | } 100 | } else { 101 | // dump_current_open_fd(); 102 | NOTICE_LOG << "accept incoming connection fail:" << ec.message() << std::endl; 103 | clean_up(); 104 | } 105 | 106 | do_accept(); 107 | }); 108 | } 109 | 110 | void Service::run() { 111 | NOTICE_LOG << "SslServer start..." << std::endl; 112 | context_pool.run(); 113 | } 114 | 115 | void Service::add_signals() { 116 | signals.add(SIGINT); 117 | signals.add(SIGTERM); 118 | #ifdef SIGQUIT 119 | signals.add(SIGQUIT); 120 | #endif 121 | signals.async_wait([this](const boost::system::error_code &ec, int sig) { 122 | // dump_current_open_fd(); 123 | context_pool.stop(); 124 | 125 | NOTICE_LOG << "Recieve signal:" << sig << " SslServer stopped..." << std::endl; 126 | }); 127 | } -------------------------------------------------------------------------------- /Server/Service.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "TlsSession.h" 3 | #include "WebsocketSession.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | using namespace boost::asio; 14 | 15 | class Service : private boost::noncopyable { 16 | public: 17 | Service(); 18 | void run(); 19 | 20 | private: 21 | void add_signals(); 22 | void do_accept(); 23 | void do_websocket_accept(); 24 | void load_server_certificate(boost::asio::ssl::context& ctx); 25 | 26 | private: 27 | IoContextPool context_pool; 28 | // ios_deque io_contexts_; 29 | boost::asio::io_context& io_context; 30 | 31 | boost::asio::signal_set signals; 32 | boost::asio::ip::tcp::acceptor acceptor_; 33 | boost::asio::ssl::context ssl_context_; 34 | public: 35 | std::shared_ptr new_connection_; 36 | std::shared_ptr websocket_connection_; 37 | }; 38 | -------------------------------------------------------------------------------- /Server/Session.cpp: -------------------------------------------------------------------------------- 1 | #include "Session.h" 2 | #include "Protocol/trojan/UDPPacket.h" 3 | #include "Shared/ConfigManage.h" 4 | #include "Shared/Log.h" 5 | #include 6 | #include 7 | #include 8 | template 9 | Session::Session(boost::asio::io_context& ioctx, boost::asio::ssl::context& sslctx) 10 | 11 | : io_context_(ioctx) 12 | , upstream_socket(ioctx, sslctx) 13 | , downstream_socket(ioctx) 14 | , resolver_(ioctx) 15 | , udp_resolver(ioctx) 16 | , downstream_udp_socket(ioctx) 17 | , in_buf(MAX_BUFF_SIZE) 18 | , out_buf(MAX_BUFF_SIZE) 19 | 20 | { 21 | } 22 | template 23 | void Session::handle_custom_protocol() 24 | { 25 | auto self = this->shared_from_this(); 26 | upstream_socket.async_read_some(boost::asio::buffer(in_buf), 27 | [this, self](boost::system::error_code ec, std::size_t length) { 28 | if (ec) { 29 | NOTICE_LOG << " Read trojan message failed:" << ec.message(); 30 | destroy(); 31 | return; 32 | } 33 | bool valid = false; 34 | if (VRequest::is_v_protocol(in_buf)) { 35 | 36 | valid = v_req.unstream(std::string(in_buf.data(), length)); 37 | password = v_req.password; 38 | vprotocol = true; 39 | } else { 40 | valid = trojanReq.parse(std::string(in_buf.data(), length)) != -1; 41 | password = trojanReq.password; 42 | } 43 | 44 | if (valid) { 45 | // 46 | if (!ConfigManage::instance().server_cfg.allowed_passwords.count(password)) { 47 | ERROR_LOG << "unsupported password from client....,end session"; 48 | destroy(); 49 | return; 50 | } 51 | 52 | } else { 53 | ERROR_LOG << "parse trojan request fail"; 54 | destroy(); 55 | return; 56 | } 57 | if (!vprotocol && trojanReq.command == TrojanReq::UDP_ASSOCIATE) { 58 | upstream_udp_buff = trojanReq.payload; 59 | handle_trojan_udp_proxy(); 60 | 61 | } else { 62 | do_resolve(); 63 | } 64 | }); 65 | } 66 | template 67 | void Session::udp_upstream_read() 68 | { 69 | auto self = this->shared_from_this(); 70 | upstream_socket.async_read_some(boost::asio::buffer(in_buf), 71 | [this, self](boost::system::error_code ec, std::size_t length) { 72 | if (!ec) { 73 | upstream_udp_buff += std::string(in_buf.data(), length); 74 | handle_trojan_udp_proxy(); 75 | 76 | } else { 77 | destroy(); 78 | } 79 | }); 80 | } 81 | template 82 | void Session::handle_trojan_udp_proxy() 83 | { 84 | state_ = FORWARD; 85 | UDPPacket udp_packet; 86 | size_t packet_len; 87 | bool is_packet_valid = udp_packet.parse(upstream_udp_buff, packet_len); 88 | if (!is_packet_valid) { 89 | if (upstream_udp_buff.length() > MAX_BUFF_SIZE) { 90 | ERROR_LOG << "parse packet get wrong UDP packet too long"; 91 | destroy(); 92 | return; 93 | } 94 | udp_upstream_read(); 95 | return; 96 | } 97 | upstream_udp_buff = upstream_udp_buff.substr(packet_len); 98 | DEBUG_LOG << "udp:" << udp_packet.address.address << ":" << udp_packet.address.port; 99 | auto self = this->shared_from_this(); 100 | udp_resolver.async_resolve(udp_packet.address.address, std::to_string(udp_packet.address.port), [this, self, udp_packet](const boost::system::error_code error, const udp::resolver::results_type& results) { 101 | if (error || results.empty()) { 102 | ERROR_LOG << "cannot resolve remote server hostname " << udp_packet.address.address << ": " << error.message(); 103 | destroy(); 104 | return; 105 | } 106 | auto iterator = results.begin(); 107 | for (auto it = results.begin(); it != results.end(); ++it) { 108 | const auto& addr = it->endpoint().address(); 109 | if (addr.is_v4()) { 110 | iterator = it; 111 | break; 112 | } 113 | } 114 | if (!downstream_udp_socket.is_open()) { 115 | auto protocol = iterator->endpoint().protocol(); 116 | boost::system::error_code ec; 117 | downstream_udp_socket.open(protocol, ec); 118 | if (ec) { 119 | destroy(); 120 | return; 121 | } 122 | } 123 | downstream_udp_socket.async_send_to(boost::asio::buffer(udp_packet.payload), *iterator, 124 | [this, self](boost::system::error_code ec, std::size_t length) { 125 | if (!ec) 126 | udp_async_bidirectional_read(3); 127 | else { 128 | if (ec != boost::asio::error::operation_aborted) { 129 | ERROR_LOG << "Server-->RemoteWeb(udp):" << ec.message(); 130 | } 131 | destroy(); 132 | return; 133 | } 134 | }); 135 | // udp_async_bidirectional_write(1, udp_packet.payload, iterator); 136 | }); 137 | } 138 | template 139 | void Session::udp_async_bidirectional_read(int direction) 140 | { 141 | auto self = this->shared_from_this(); 142 | // We must divide reads by direction to not permit second read call on the same socket. 143 | if (direction & 0x01) 144 | upstream_socket.async_read_some(boost::asio::buffer(in_buf), 145 | [this, self](boost::system::error_code ec, std::size_t length) { 146 | if (!ec) { 147 | DEBUG_LOG << "--> " << std::to_string(length) << " bytes"; 148 | UDPPacket udp_packet; 149 | size_t packet_len; 150 | upstream_udp_buff += std::string(in_buf.data(), length); 151 | bool is_packet_valid = udp_packet.parse(upstream_udp_buff, packet_len); 152 | if (is_packet_valid) { 153 | upstream_udp_buff = upstream_udp_buff.substr(packet_len); 154 | 155 | udp_resolver.async_resolve(udp_packet.address.address, std::to_string(udp_packet.address.port), [this, self, udp_packet](const boost::system::error_code error, const udp::resolver::results_type& results) { 156 | if (error || results.empty()) { 157 | ERROR_LOG << "cannot resolve remote server hostname " << udp_packet.address.address << ": " << error.message(); 158 | destroy(); 159 | return; 160 | } 161 | auto iterator = results.begin(); 162 | for (auto it = results.begin(); it != results.end(); ++it) { 163 | const auto& addr = it->endpoint().address(); 164 | if (addr.is_v4()) { 165 | iterator = it; 166 | break; 167 | } 168 | } 169 | 170 | udp_async_bidirectional_write(1, udp_packet.payload, iterator); 171 | }); 172 | } else { 173 | if (upstream_udp_buff.length() > MAX_BUFF_SIZE) { 174 | ERROR_LOG << "parse packet get wrong UDP packet too long"; 175 | destroy(); 176 | return; 177 | } 178 | udp_async_bidirectional_read(1); 179 | } 180 | 181 | } else // if (ec != boost::asio::error::eof) 182 | { 183 | if (ec != boost::asio::error::eof && ec != boost::asio::error::operation_aborted) { 184 | ERROR_LOG << "client-->Server(UDP over tls) " << ec.message(); 185 | } 186 | 187 | destroy(); 188 | return; 189 | } 190 | }); 191 | 192 | if (direction & 0x2) { 193 | udp::endpoint udp_sender_endpoint; 194 | downstream_udp_socket.async_receive_from(boost::asio::buffer(out_buf), udp_sender_endpoint, 195 | [this, self, udp_sender_endpoint](boost::system::error_code ec, std::size_t length) { 196 | if (!ec) { 197 | 198 | DEBUG_LOG << "<-- " << std::to_string(length) << " bytes"; 199 | auto packet = UDPPacket::generate(udp_sender_endpoint, std::string(out_buf.data(), length)); 200 | 201 | udp_async_bidirectional_write(2, packet, boost::asio::ip::udp::resolver::iterator()); 202 | } else // if (ec != boost::asio::error::eof) 203 | { 204 | if (ec != boost::asio::error::eof && ec != boost::asio::error::operation_aborted) { 205 | ERROR_LOG << "Server<--RemoteWeb(udp): " << ec.message(); 206 | } 207 | 208 | 209 | destroy(); 210 | return; 211 | } 212 | }); 213 | } 214 | } 215 | template 216 | void Session::udp_async_bidirectional_write(int direction, const std::string& packet, boost::asio::ip::udp::resolver::iterator udp_ep) 217 | { 218 | auto self(this->shared_from_this()); 219 | 220 | switch (direction) { 221 | case 1: 222 | downstream_udp_socket.async_send_to(boost::asio::buffer(packet), *udp_ep, 223 | [this, self, direction](boost::system::error_code ec, std::size_t length) { 224 | if (!ec) 225 | udp_async_bidirectional_read(direction); 226 | else { 227 | if (ec != boost::asio::error::operation_aborted) { 228 | ERROR_LOG << "Server-->RemoteWeb(udp):" << ec.message(); 229 | } 230 | 231 | destroy(); 232 | return; 233 | } 234 | }); 235 | break; 236 | case 2: 237 | upstream_udp_write(direction, packet); 238 | break; 239 | } 240 | } 241 | template 242 | void Session::do_resolve() 243 | { 244 | auto self(this->shared_from_this()); 245 | remote_host = vprotocol ? v_req.address : trojanReq.address.address; 246 | remote_port = std::to_string(vprotocol ? v_req.port : trojanReq.address.port); 247 | resolver_.async_resolve(tcp::resolver::query(remote_host, remote_port), 248 | [this, self](const boost::system::error_code& ec, tcp::resolver::iterator it) { 249 | if (!ec) { 250 | do_connect(it); 251 | } else { 252 | ERROR_LOG << "failed to resolve " << remote_host << ":" << remote_port << " " << ec.message(); 253 | destroy(); 254 | } 255 | }); 256 | } 257 | template 258 | void Session::do_connect(tcp::resolver::iterator& it) 259 | { 260 | auto self(this->shared_from_this()); 261 | state_ = FORWARD; 262 | downstream_socket.async_connect(*it, 263 | [this, self, it](const boost::system::error_code& ec) { 264 | if (!ec) { 265 | boost::asio::socket_base::keep_alive option(true); 266 | downstream_socket.set_option(option); 267 | DEBUG_LOG << "connected to " << remote_host << ":" << remote_port; 268 | 269 | if (vprotocol && !v_req.packed_buff.empty() || !vprotocol && !trojanReq.payload.empty()) { 270 | DEBUG_LOG << "payload not empty"; 271 | 272 | boost::asio::async_write(downstream_socket, boost::asio::buffer(vprotocol ? v_req.packed_buff : trojanReq.payload), 273 | [this, self](boost::system::error_code ec, std::size_t length) { 274 | if (!ec) 275 | async_bidirectional_read(3); 276 | else { 277 | ERROR_LOG << "Client<--Server:" << ec.message(); 278 | destroy(); 279 | return; 280 | } 281 | }); 282 | } 283 | // read packet from both direction 284 | else 285 | async_bidirectional_read(3); 286 | 287 | } else { 288 | ERROR_LOG << "failed to connect " << remote_host << ":" << remote_port << " " << ec.message(); 289 | destroy(); 290 | } 291 | }); 292 | } 293 | template 294 | void Session::async_bidirectional_read(int direction) 295 | { 296 | auto self = this->shared_from_this(); 297 | // We must divide reads by direction to not permit second read call on the same socket. 298 | if (direction & 0x01) 299 | upstream_socket.async_read_some(boost::asio::buffer(in_buf), 300 | [this, self](boost::system::error_code ec, std::size_t length) { 301 | if (!ec) { 302 | DEBUG_LOG << "--> " << std::to_string(length) << " bytes"; 303 | 304 | async_bidirectional_write(1, length); 305 | } else // if (ec != boost::asio::error::eof) 306 | { 307 | if (ec != boost::asio::error::eof && ec != boost::asio::error::operation_aborted) { 308 | ERROR_LOG << "Client-->Server: " << ec.message(); 309 | } 310 | destroy(); 311 | return; 312 | } 313 | }); 314 | 315 | if (direction & 0x2) 316 | downstream_socket.async_read_some(boost::asio::buffer(out_buf), 317 | [this, self](boost::system::error_code ec, std::size_t length) { 318 | if (!ec) { 319 | 320 | DEBUG_LOG << "<-- " << std::to_string(length) << " bytes"; 321 | 322 | async_bidirectional_write(2, length); 323 | } else // if (ec != boost::asio::error::eof) 324 | { 325 | if (ec != boost::asio::error::eof && ec != boost::asio::error::operation_aborted) { 326 | ERROR_LOG << "Server<--RemoteWeb: " << ec.message(); 327 | } 328 | destroy(); 329 | return; 330 | } 331 | }); 332 | } 333 | template 334 | void Session::async_bidirectional_write(int direction, size_t len) 335 | { 336 | auto self =this->shared_from_this(); 337 | 338 | switch (direction) { 339 | case 1: 340 | boost::asio::async_write(downstream_socket,boost::asio::buffer(in_buf, len), 341 | [this, self, direction](boost::system::error_code ec, std::size_t length) { 342 | if (!ec) 343 | async_bidirectional_read(direction); 344 | else { 345 | if (ec != boost::asio::error::operation_aborted) { 346 | ERROR_LOG << "Server-->Webserver:" << ec.message(); 347 | } 348 | destroy(); 349 | return; 350 | } 351 | }); 352 | break; 353 | case 2: 354 | upstream_tcp_write(direction, len); 355 | break; 356 | } 357 | } 358 | 359 | template 360 | void Session::upstream_tcp_write(int direction, size_t len) 361 | { 362 | assert(0); 363 | } 364 | template 365 | void Session::upstream_udp_write(int direction, const std::string& packet) 366 | { 367 | assert(0); 368 | } 369 | template 370 | void Session::destroy() 371 | { 372 | DEBUG_LOG<<"Session destroyed called"; 373 | boost::system::error_code ec; 374 | if (downstream_udp_socket.is_open()) { 375 | downstream_udp_socket.cancel(ec); 376 | downstream_udp_socket.close(); 377 | } 378 | if (downstream_socket.is_open()) { 379 | downstream_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); 380 | downstream_socket.cancel(); 381 | downstream_socket.close(); 382 | } 383 | } -------------------------------------------------------------------------------- /Server/Session.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_SESSION_H 2 | #define SERVER_SESSION_H 3 | #include "Protocol/trojan/TrojanReq.h" 4 | #include "Protocol/trojan/UDPPacket.h" 5 | #include "Shared/Log.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace beast = boost::beast; // from 25 | namespace http = beast::http; // from 26 | namespace websocket = beast::websocket; // from 27 | namespace net = boost::asio; // from 28 | namespace ssl = boost::asio::ssl; // from 29 | using tcp = boost::asio::ip::tcp; // from 30 | using udp = boost::asio::ip::udp; 31 | 32 | using SSLSocket = boost::asio::ssl::stream; 33 | 34 | 35 | 36 | template 37 | class Session :public std::enable_shared_from_this> 38 | , private boost::noncopyable 39 | { 40 | public: 41 | enum State { 42 | HANDSHAKE, 43 | FORWARD, 44 | DESTROY 45 | }; 46 | public: 47 | Session(boost::asio::io_context&, boost::asio::ssl::context&); 48 | 49 | virtual ~Session(){} 50 | 51 | 52 | void async_bidirectional_read(int direction); 53 | 54 | void handle_custom_protocol(); 55 | 56 | void do_resolve(); 57 | 58 | void do_connect(tcp::resolver::iterator&); 59 | void udp_upstream_read(); 60 | 61 | void async_bidirectional_write(int, size_t); 62 | void handle_trojan_udp_proxy(); 63 | void udp_async_bidirectional_read(int direction); 64 | void udp_async_bidirectional_write(int, const std::string&, boost::asio::ip::udp::resolver::iterator); 65 | 66 | virtual void upstream_tcp_write(int direction, size_t len); 67 | virtual void upstream_udp_write(int direction, const std::string& packet); 68 | virtual void destroy(); 69 | 70 | protected: 71 | static constexpr size_t MAX_BUFF_SIZE = 8192; 72 | boost::asio::io_context& io_context_; 73 | T upstream_socket; 74 | tcp::socket downstream_socket; 75 | 76 | // 77 | tcp::resolver resolver_; 78 | udp::resolver udp_resolver; 79 | udp::socket downstream_udp_socket; // 80 | 81 | State state_ { HANDSHAKE }; 82 | TrojanReq trojanReq {}; 83 | VRequest v_req {}; 84 | std::string password; 85 | std::string upstream_udp_buff; 86 | // 87 | std::vector in_buf; 88 | std::vector out_buf; 89 | std::string remote_host; 90 | std::string remote_port; 91 | bool vprotocol = false; 92 | 93 | }; 94 | #include "Session.cpp" 95 | #endif -------------------------------------------------------------------------------- /Server/TlsSession.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "TlsSession.h" 3 | 4 | TlsSession::TlsSession(boost::asio::io_context& ioctx, boost::asio::ssl::context& sslctx) 5 | : Session(ioctx, sslctx) 6 | { 7 | } 8 | TlsSession::~TlsSession() 9 | { 10 | } 11 | void TlsSession::start() 12 | { 13 | auto self = shared_from_this(); 14 | upstream_socket.async_handshake(boost::asio::ssl::stream_base::server, [this, self](const boost::system::error_code& error) { 15 | if (error) { 16 | ERROR_LOG << "SSL handshake failed: " << error.message(); 17 | destroy(); 18 | return; 19 | } 20 | handle_custom_protocol(); 21 | }); 22 | } 23 | 24 | boost::asio::ip::tcp::socket& TlsSession::socket() 25 | { 26 | return upstream_socket.next_layer(); 27 | } 28 | 29 | void TlsSession::upstream_tcp_write(int direction, size_t len) 30 | { 31 | auto self(this->shared_from_this()); 32 | boost::asio::async_write(upstream_socket,boost::asio::buffer(out_buf, len), [this, self, direction](boost::system::error_code ec, std::size_t length) { 33 | if (!ec) 34 | async_bidirectional_read(direction); 35 | else { 36 | if (ec != boost::asio::error::operation_aborted) { 37 | ERROR_LOG << "Client<--Server(TCP)"<< ec.message(); 38 | } 39 | // Most probably remote server closed socket. Let's close both sockets and exit session. 40 | destroy(); 41 | return; 42 | } 43 | }); 44 | } 45 | 46 | void TlsSession::upstream_udp_write(int direction, const std::string& packet) 47 | { 48 | auto self(this->shared_from_this()); 49 | boost::asio::async_write(upstream_socket,boost::asio::buffer(packet), 50 | [this, self, direction](boost::system::error_code ec, std::size_t length) { 51 | if (!ec) 52 | udp_async_bidirectional_read(direction); 53 | else { 54 | if (ec != boost::asio::error::operation_aborted) { 55 | ERROR_LOG << "Client<--Server(UDP over tls)"<< ec.message(); 56 | } 57 | // Most probably remote server closed socket. Let's close both sockets and exit session. 58 | destroy(); 59 | return; 60 | } 61 | }); 62 | } 63 | void TlsSession::destroy() 64 | { 65 | if (state_ == DESTROY) { 66 | return; 67 | } 68 | DEBUG_LOG<<"TLS Session destroyed called"; 69 | state_ = DESTROY; 70 | Session::destroy(); 71 | if (upstream_socket.lowest_layer().is_open()) { 72 | boost::system::error_code ec; 73 | upstream_socket.lowest_layer().cancel(ec); 74 | upstream_socket.lowest_layer().shutdown(tcp::socket::shutdown_both, ec); 75 | upstream_socket.lowest_layer().close(ec); 76 | } 77 | 78 | 79 | } -------------------------------------------------------------------------------- /Server/TlsSession.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include"Session.h" 4 | 5 | class TlsSession :public Session { 6 | 7 | public: 8 | TlsSession(boost::asio::io_context&, boost::asio::ssl::context&); 9 | 10 | ~TlsSession(); 11 | 12 | void start(); 13 | 14 | 15 | boost::asio::ip::tcp::socket& socket(); 16 | 17 | virtual void upstream_tcp_write(int direction, size_t len); 18 | virtual void upstream_udp_write(int direction, const std::string& packet); 19 | virtual void destroy(); 20 | 21 | }; -------------------------------------------------------------------------------- /Server/WebsocketSession.cpp: -------------------------------------------------------------------------------- 1 | #include "WebsocketSession.h" 2 | #include "Shared/ConfigManage.h" 3 | #include "Shared/Log.h" 4 | WebsocketSession::WebsocketSession(boost::asio::io_context& io_ctx, boost::asio::ssl::context& ssl_ctx) 5 | : Session>>(io_ctx,ssl_ctx) 6 | { 7 | } 8 | void WebsocketSession::start() 9 | { 10 | // Set the timeout. 11 | beast::get_lowest_layer(upstream_socket).expires_after(std::chrono::seconds(30)); 12 | 13 | // Perform the SSL handshake 14 | auto self = shared_from_this(); 15 | upstream_socket.next_layer().async_handshake( 16 | ssl::stream_base::server, 17 | [this,self](beast::error_code ec){ 18 | on_handshake(ec); 19 | }); 20 | } 21 | void WebsocketSession::on_handshake(beast::error_code ec) 22 | { 23 | if (ec) { 24 | ERROR_LOG << "SSL handshak failed:" << ec.message(); 25 | return; 26 | } 27 | // Turn off the timeout on the tcp_stream, because 28 | // the websocket stream has its own timeout system. 29 | beast::get_lowest_layer(upstream_socket).expires_never(); 30 | 31 | // Set suggested timeout settings for the websocket 32 | upstream_socket.set_option( 33 | websocket::stream_base::timeout::suggested( 34 | beast::role_type::server)); 35 | 36 | // Set a decorator to change the Server of the handshake 37 | upstream_socket.set_option(websocket::stream_base::decorator( 38 | [](websocket::response_type& res) { 39 | res.set(http::field::server,"overplus websocket-server"); 40 | })); 41 | 42 | // Accept the websocket handshake 43 | auto self = shared_from_this(); 44 | upstream_socket.async_accept( 45 | [this,self](beast::error_code ec){ 46 | on_accept(ec); 47 | } 48 | ); 49 | } 50 | void WebsocketSession::on_accept(beast::error_code ec) 51 | { 52 | if (ec) { 53 | ERROR_LOG << "websocket accept failed:" << ec.message(); 54 | return; 55 | } 56 | handle_custom_protocol(); 57 | } 58 | void WebsocketSession::upstream_tcp_write(int direction, size_t len) 59 | { 60 | auto self(this->shared_from_this()); 61 | upstream_socket.async_write(boost::asio::buffer(out_buf, len), [this, self, direction](boost::system::error_code ec, std::size_t length) { 62 | if (!ec) 63 | async_bidirectional_read(direction); 64 | else { 65 | if (ec != boost::asio::error::operation_aborted) { 66 | NOTICE_LOG << "Client<--Server(TCP):" <shared_from_this()); 77 | upstream_socket.async_write(boost::asio::buffer(packet), 78 | [this, self, direction](boost::system::error_code ec, std::size_t length) { 79 | if (!ec) 80 | udp_async_bidirectional_read(direction); 81 | else { 82 | if (ec != boost::asio::error::operation_aborted) { 83 | NOTICE_LOG << "Client<--Server(UDP over tls)" << ec.message(); 84 | } 85 | destroy(); 86 | return; 87 | } 88 | }); 89 | } 90 | void WebsocketSession::destroy() 91 | { 92 | if (state_ == DESTROY) { 93 | return; 94 | } 95 | state_ = DESTROY; 96 | Session>>::destroy(); 97 | } 98 | -------------------------------------------------------------------------------- /Server/WebsocketSession.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include"Session.h" 3 | 4 | class WebsocketSession : public Session>>{ 5 | 6 | public: 7 | WebsocketSession(boost::asio::io_context&, boost::asio::ssl::context&); 8 | ~WebsocketSession(){ 9 | } 10 | void start(); 11 | 12 | void on_handshake(beast::error_code ec); 13 | void on_accept(beast::error_code ec); 14 | boost::asio::ip::tcp::socket& socket() 15 | { 16 | return beast::get_lowest_layer(upstream_socket).socket(); 17 | } 18 | 19 | virtual void upstream_tcp_write(int direction, size_t len); 20 | virtual void upstream_udp_write(int direction, const std::string& packet); 21 | virtual void destroy(); 22 | 23 | }; -------------------------------------------------------------------------------- /Server/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Server/Service.h" 2 | #include "Shared/ConfigManage.h" 3 | // #include 4 | #include "Shared/Log.h" 5 | #include "Shared/LogFile.h" 6 | #include "Shared/Version.h" 7 | #include 8 | #include 9 | #include 10 | namespace po = boost::program_options; 11 | int main(int argc, char* argv[]) 12 | { 13 | try { 14 | std::string config_file; 15 | po::options_description desc("Allowed options"); 16 | desc.add_options() // 17 | ("version,v", "print version string") // 18 | ("help,h", "print help message") // 19 | ("config,c", po::value(&config_file)->default_value("server.json"), "config file path"); 20 | po::variables_map vm; 21 | po::store(po::parse_command_line(argc, argv, desc), vm); 22 | po::notify(vm); 23 | if (vm.count("help")) { 24 | std::cout << desc << std::endl; 25 | return 0; 26 | } 27 | if (vm.count("version")) { 28 | std::cout << OVERPLUS_VERSION_STR << std::endl; 29 | return 0; 30 | } 31 | auto& config = ConfigManage::instance(); 32 | config.load_config(config_file, ConfigManage::Server); 33 | std::unique_ptr logfile_; 34 | 35 | logger::set_log_level(config.server_cfg.log_level); 36 | if (!config.server_cfg.log_dir.empty()) { 37 | logfile_.reset(new LogFile("server", 10 * 1024 * 1024)); 38 | logger::set_log_destination(Destination::D_FILE); 39 | logger::setOutput([&](std::string&& buf) { 40 | logfile_->append(std::move(buf)); 41 | }); 42 | logger::setFlush([&]() { 43 | logfile_->flush(); 44 | }); 45 | } 46 | 47 | NOTICE_LOG << "overplus " << OVERPLUS_VERSION_STR << " will start..." << std::endl; 48 | Service server; 49 | server.run(); 50 | } catch (const std::exception& e) { 51 | ERROR_LOG << "Server got exception,will exit :" << e.what(); 52 | } 53 | NOTICE_LOG << "Server Stopped,will exit"; 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /Shared/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | message(STATUS "Enter Shared CMakeList ...") 2 | aux_source_directory(. shared_SRCS) 3 | add_library(shared ${shared_SRCS}) 4 | -------------------------------------------------------------------------------- /Shared/Coding.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Shared/Coding.h" 3 | #include 4 | #include 5 | 6 | void Coding::EncodeFixed8(std::string &buf, uint8_t value) { 7 | buf.append(1, value); 8 | } 9 | 10 | void Coding::EncodeFixed16(std::string &dst, uint16_t value) { 11 | char buffer[2]; 12 | // Recent clang and gcc optimize this to a single mov / str instruction. 13 | buffer[0] = static_cast(value); 14 | buffer[1] = static_cast(value >> 8); 15 | dst.append(buffer, 2); 16 | } 17 | 18 | void Coding::EncodeFixed32(std::string &dst, uint32_t value) { 19 | char buffer[4]; 20 | // Recent clang and gcc optimize this to a single mov / str instruction. 21 | buffer[0] = static_cast(value); 22 | buffer[1] = static_cast(value >> 8); 23 | buffer[2] = static_cast(value >> 16); 24 | buffer[3] = static_cast(value >> 24); 25 | dst.append(buffer, 4); 26 | } 27 | 28 | void Coding::EncodeStr(std::string &dst, std::string &src) { 29 | dst.append(src); 30 | } 31 | 32 | void Coding::EncodeCstr(std::string &dst, const char *src) { 33 | dst.append(src); 34 | } 35 | 36 | uint8_t Coding::DecodeFixed8(const char *ptr) { 37 | 38 | const uint8_t *buff = reinterpret_cast(ptr); 39 | return static_cast(buff[0]); 40 | } 41 | 42 | uint16_t Coding::DecodeFixed16(const char *ptr) { 43 | const uint8_t *buff = reinterpret_cast(ptr); 44 | return static_cast(buff[0]) | static_cast(buff[1]) << 8; 45 | } 46 | 47 | uint32_t Coding::DecodeFixed32(const char *ptr) { 48 | const uint8_t *buff = reinterpret_cast(ptr); 49 | return static_cast(buff[0]) 50 | | static_cast(buff[1]) << 8 51 | | static_cast(buff[2]) << 16 52 | | static_cast(buff[3]) << 24; 53 | } 54 | 55 | std::string Coding::DecodeStr(const char *ptr, int len) { 56 | return std::string(ptr, len); 57 | } -------------------------------------------------------------------------------- /Shared/Coding.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | class Coding { 6 | public: 7 | static void EncodeFixed8(std::string& buf, uint8_t value); 8 | 9 | static void EncodeFixed16(std::string& dst, uint16_t value); 10 | 11 | static void EncodeFixed32(std::string& dst, uint32_t value); 12 | 13 | static void EncodeStr(std::string& dst, std::string& src); 14 | static void EncodeCstr(std::string& dst, const char* src); 15 | 16 | // 17 | static uint8_t DecodeFixed8(const char*ptr); 18 | static uint16_t DecodeFixed16(const char*ptr); 19 | static uint32_t DecodeFixed32(const char*ptr); 20 | static std::string DecodeStr(const char*ptr,int len); 21 | }; 22 | -------------------------------------------------------------------------------- /Shared/ConfigManage.cpp: -------------------------------------------------------------------------------- 1 | #include "ConfigManage.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // 9 | static std::string SHA224(const std::string& message) 10 | { 11 | uint8_t digest[EVP_MAX_MD_SIZE]; 12 | char mdString[(EVP_MAX_MD_SIZE << 1) + 1]; 13 | unsigned int digest_len; 14 | EVP_MD_CTX* ctx; 15 | if ((ctx = EVP_MD_CTX_new()) == nullptr) { 16 | throw std::runtime_error("could not create hash context"); 17 | } 18 | if (!EVP_DigestInit_ex(ctx, EVP_sha224(), nullptr)) { 19 | EVP_MD_CTX_free(ctx); 20 | throw std::runtime_error("could not initialize hash context"); 21 | } 22 | if (!EVP_DigestUpdate(ctx, message.c_str(), message.length())) { 23 | EVP_MD_CTX_free(ctx); 24 | throw std::runtime_error("could not update hash"); 25 | } 26 | if (!EVP_DigestFinal_ex(ctx, digest, &digest_len)) { 27 | EVP_MD_CTX_free(ctx); 28 | throw std::runtime_error("could not output hash"); 29 | } 30 | 31 | for (unsigned int i = 0; i < digest_len; ++i) { 32 | sprintf(mdString + (i << 1), "%02x", (unsigned int)digest[i]); 33 | } 34 | mdString[digest_len << 1] = '\0'; 35 | EVP_MD_CTX_free(ctx); 36 | return std::string(mdString); 37 | } 38 | 39 | ConfigManage& ConfigManage::instance() 40 | { 41 | static ConfigManage instance; 42 | return instance; 43 | } 44 | void ConfigManage::load_config(const std::string& path, ConfigType type) 45 | { 46 | boost::property_tree::ptree tree; 47 | read_json(path, tree); 48 | 49 | if (type == ConfigType::Server) { 50 | server_cfg.populate(tree); 51 | } 52 | else if(type == ConfigType::Client) 53 | { 54 | client_cfg.populate(tree); 55 | 56 | } 57 | else { 58 | ERROR_LOG << "Expected server type config"; 59 | } 60 | loaded = true; 61 | 62 | } 63 | void ClientConfig::populate(boost::property_tree::ptree& tree) 64 | { 65 | local_addr = tree.get("local_addr", std::string()); 66 | local_port = tree.get("local_port", std::string()); 67 | remote_addr = tree.get("remote_addr", std::string()); 68 | remote_port = tree.get("remote_port", std::string()); 69 | user_name = tree.get("user_name", std::string()); 70 | text_password = tree.get("password", std::string()); 71 | password = SHA224(text_password); 72 | } 73 | void ClientConfig::setPassword(std::string&psswd) 74 | { 75 | password = SHA224(psswd); 76 | text_password = psswd; 77 | } 78 | void ServerConfig::populate(boost::property_tree::ptree& tree) 79 | { 80 | local_addr = tree.get("local_addr", std::string()); 81 | local_port = tree.get("local_port", std::string()); 82 | 83 | std::unordered_set().swap(allowed_passwords); 84 | if (tree.get_child_optional("allowed_passwords")) { 85 | for (auto& item : tree.get_child("allowed_passwords")) { 86 | std::string p = item.second.get_value(); 87 | allowed_passwords.insert(SHA224(p)); 88 | } 89 | } 90 | 91 | auto str_leve = tree.get("log_level", std::string()); 92 | if (str_leve == "DEBUG") { 93 | log_level = L_DEBUG; 94 | } else if (str_leve == "NOTICE") { 95 | log_level = L_NOTICE; 96 | } else { 97 | log_level = L_ERROR_EXIT; 98 | } 99 | setLogLevel(log_level); 100 | log_dir = tree.get("log_dir", std::string()); 101 | 102 | certificate_chain = tree.get("ssl.cert", std::string()); 103 | server_private_key = tree.get("ssl.key", std::string()); 104 | websocketEnabled = tree.get("websocketEnabled",false); 105 | } 106 | -------------------------------------------------------------------------------- /Shared/ConfigManage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Log.h" 3 | #include 4 | #include 5 | 6 | struct ServerConfig { 7 | // json format 8 | /* 9 | { 10 | "run_type": "server", 11 | "local_addr": "0.0.0.0", 12 | "local_port": 443, 13 | "allowed_password": [ 14 | "password1", 15 | "password2" 16 | ], 17 | #NOTICE DEBUG ERROR 18 | "log_level": "NOTICE", 19 | "log_dir":"", 20 | "ssl": { 21 | "cert": "/path/to/certificate.crt", 22 | "key": "/path/to/private.key", 23 | } 24 | } 25 | */ 26 | std::string local_addr; 27 | std::string local_port; 28 | std::unordered_set allowed_passwords; 29 | // 30 | Loglevel log_level; 31 | std::string log_dir; 32 | std::string certificate_chain; 33 | std::string server_private_key; 34 | // 35 | //websocket 36 | bool websocketEnabled; 37 | void populate(boost::property_tree::ptree&); 38 | }; 39 | struct ClientConfig { 40 | // json format 41 | /* 42 | { 43 | "run_type": "client", 44 | "local_addr": "0.0.0.0", 45 | "local_port": 1080, 46 | "remote_addr": "1.2.3.4", 47 | "remote_port": 443, 48 | "user_name":"user1", 49 | "password": "password1" 50 | 51 | #NOTICE DEBUG ERROR 52 | "log_level": "NOTICE", 53 | } 54 | */ 55 | std::string local_addr; 56 | std::string local_port; 57 | std::string remote_addr; 58 | std::string remote_port; 59 | std::string password; 60 | std::string user_name; 61 | std::string text_password; 62 | 63 | void populate(boost::property_tree::ptree&); 64 | void setPassword(std::string&psswd); 65 | 66 | }; 67 | class ConfigManage : private boost::noncopyable { 68 | 69 | public: 70 | enum ConfigType { 71 | Server, 72 | Client 73 | 74 | }; 75 | 76 | void load_config(const std::string&, ConfigType); 77 | static ConfigManage& instance(); 78 | 79 | public: 80 | ClientConfig client_cfg; 81 | ServerConfig server_cfg; 82 | bool loaded=false; 83 | 84 | private: 85 | ConfigManage() = default; 86 | }; 87 | -------------------------------------------------------------------------------- /Shared/IoContextPool.cpp: -------------------------------------------------------------------------------- 1 | #include "IoContextPool.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | IoContextPool::IoContextPool(std::size_t pool_size) 8 | : next_io_context_(0) 9 | { 10 | if (pool_size == 0) 11 | throw std::runtime_error("IoContextPool size is 0"); 12 | 13 | // Give all the io_contexts work to do so that their run() functions will not 14 | // exit until they are explicitly stopped. 15 | for (std::size_t i = 0; i < pool_size; ++i) { 16 | io_context_ptr io_context(new boost::asio::io_context()); 17 | io_contexts_.push_back(io_context); 18 | work_.push_back(boost::asio::make_work_guard(*io_context)); 19 | } 20 | } 21 | 22 | void IoContextPool::run() 23 | { 24 | // Create a pool of threads to run all of the io_contexts. 25 | std::vector> threads; 26 | for (unsigned int i = 0; i < io_contexts_.size(); ++i) { 27 | std::shared_ptr thread(new std::thread([this, i] { 28 | io_contexts_[i]->run(); 29 | })); 30 | threads.push_back(thread); 31 | } 32 | 33 | // Wait for all threads in the pool to exit. 34 | for (std::size_t i = 0; i < threads.size(); ++i) 35 | threads[i]->join(); 36 | } 37 | 38 | void IoContextPool::stop() 39 | { 40 | // Explicitly stop all io_contexts. 41 | for (std::size_t i = 0; i < io_contexts_.size(); ++i) 42 | io_contexts_[i]->stop(); 43 | } 44 | 45 | boost::asio::io_context& IoContextPool::get_io_context() 46 | { 47 | // Use a round-robin scheme to choose the next io_context to use. 48 | boost::asio::io_context& io_context = *io_contexts_[next_io_context_]; 49 | ++next_io_context_; 50 | if (next_io_context_ == io_contexts_.size()) 51 | next_io_context_ = 0; 52 | return io_context; 53 | } 54 | -------------------------------------------------------------------------------- /Shared/IoContextPool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /// A pool of io_context objects. 10 | class IoContextPool 11 | : private boost::noncopyable { 12 | public: 13 | /// Construct the io_context pool. 14 | explicit IoContextPool(std::size_t pool_size); 15 | 16 | /// Run all io_context objects in the pool. 17 | void run(); 18 | 19 | /// Stop all io_context objects in the pool. 20 | void stop(); 21 | 22 | /// Get an io_context to use. 23 | boost::asio::io_context& get_io_context(); 24 | 25 | private: 26 | typedef boost::shared_ptr io_context_ptr; 27 | typedef boost::asio::executor_work_guard< 28 | boost::asio::io_context::executor_type> 29 | io_context_work; 30 | 31 | /// The pool of io_contexts. 32 | std::vector io_contexts_; 33 | 34 | /// The work that keeps the io_contexts running. 35 | std::list work_; 36 | 37 | /// The next io_context to use for a connection. 38 | std::size_t next_io_context_; 39 | }; 40 | -------------------------------------------------------------------------------- /Shared/Log.cpp: -------------------------------------------------------------------------------- 1 | #include "Log.h" 2 | #include 3 | #include 4 | #include 5 | #define ANSI_COLOR_RED "\x1b[31m" 6 | #define ANSI_COLOR_GREEN "\x1b[32m" 7 | #define ANSI_COLOR_YELLOW "\x1b[33m" 8 | #define ANSI_COLOR_BLUE "\x1b[34m" 9 | #define ANSI_COLOR_MAGENTA "\x1b[35m" 10 | #define ANSI_COLOR_CYAN "\x1b[36m" 11 | #define ANSI_COLOR_RESET "\x1b[0m" 12 | 13 | Loglevel log_level = L_NOTICE; 14 | static Destination log_dest = D_STDOUT; 15 | void setLogLevel(Loglevel& level) 16 | { 17 | log_level = level; 18 | } 19 | static std::function flush_ = []() { 20 | std::flush(std::cout); 21 | }; 22 | static std::function output_ = [](std::string&& buf) { 23 | std::cout << buf; 24 | }; 25 | const char* level_str[] { 26 | "[DEBUG] ", 27 | "[NOTICE] ", 28 | "[ERROR] " 29 | 30 | }; 31 | 32 | static std::string get_format_time() 33 | { 34 | time_t timep; 35 | time(&timep); 36 | char tmp[64]; 37 | std::strftime(tmp, sizeof(tmp), "[%Y-%m-%d %H:%M:%S]", std::localtime(&timep)); 38 | return tmp; 39 | } 40 | void logger::setOutput(std::function&& outputFunc) 41 | { 42 | output_ = outputFunc; 43 | } 44 | void logger::setFlush(std::function&& flush) 45 | { 46 | flush_ = flush; 47 | } 48 | void logger::set_log_level(Loglevel level) 49 | { 50 | log_level = level; 51 | } 52 | Loglevel logger::get_log_level() 53 | { 54 | return log_level; 55 | } 56 | void logger::set_log_destination(Destination dest) 57 | { 58 | log_dest = dest; 59 | } 60 | logger::~logger() 61 | { 62 | // auto buf = impl.log_stream_.str(); 63 | impl.log_stream_ << "\n"; 64 | output_(impl.log_stream_.str()); 65 | if (impl.level_ == L_ERROR_EXIT) { 66 | flush_(); 67 | // abort(); 68 | } 69 | } 70 | logger::logger(const char* file, const char* func, int line, Loglevel level) 71 | : impl(file, func, line, level) 72 | { 73 | } 74 | logger::logger(Loglevel level) 75 | : impl(level) 76 | { 77 | } 78 | logger::Impl::Impl(const char* file, const char* func, int line, Loglevel level) 79 | { 80 | if (log_dest == D_STDOUT) { 81 | log_stream_ << get_format_time() << ANSI_COLOR_GREEN << level_str[level] << ANSI_COLOR_RESET << file << " line: " << line << " "; 82 | } else { 83 | log_stream_ << get_format_time() << level_str[level] << file <<" line: " << line << " "; 84 | } 85 | log_stream_ << "thread[" << std::this_thread::get_id() << "] "; 86 | 87 | level_ = level; 88 | } 89 | 90 | logger::Impl::Impl(Loglevel level) 91 | { 92 | if (log_dest == D_STDOUT) { 93 | log_stream_ << get_format_time() << ANSI_COLOR_RED << level_str[level] << ANSI_COLOR_RESET << " "; 94 | 95 | } else { 96 | log_stream_ << get_format_time() << level_str[level] << " "; 97 | } 98 | log_stream_ << "[" << std::this_thread::get_id() << "] "; 99 | level_ = level; 100 | } 101 | logger::Impl::~Impl() 102 | { 103 | // log_stream_ << "\n"; 104 | } 105 | -------------------------------------------------------------------------------- /Shared/Log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | enum Loglevel : uint8_t { 8 | L_DEBUG, 9 | L_NOTICE, 10 | L_ERROR_EXIT 11 | }; 12 | enum Destination { 13 | D_STDOUT, 14 | D_FILE 15 | 16 | }; 17 | class logger { 18 | public: 19 | logger(const char* file, const char* func, int line, Loglevel level); 20 | logger(Loglevel level); 21 | 22 | ~logger(); 23 | std::ostream& stream() { return impl.log_stream_; } 24 | static void setOutput(std::function&& outputFunc); 25 | static void setFlush(std::function&& fulsh); 26 | static void set_log_level(Loglevel level); 27 | static Loglevel get_log_level(); 28 | static void set_log_destination(Destination dest); 29 | 30 | private: 31 | struct Impl { 32 | Impl(const char* file, const char* func, int line, Loglevel level); 33 | Impl(Loglevel level); 34 | ~Impl(); 35 | std::ostringstream log_stream_; 36 | Loglevel level_; 37 | }; 38 | Impl impl; 39 | }; 40 | void setLogLevel(Loglevel& level); 41 | 42 | #define DEBUG_LOG \ 43 | if (logger::get_log_level() <= L_DEBUG) \ 44 | logger(__FILE__, __func__, __LINE__, L_DEBUG).stream() 45 | #define NOTICE_LOG \ 46 | if (logger::get_log_level() <= L_NOTICE) \ 47 | logger(__FILE__, __func__, __LINE__, L_NOTICE).stream() 48 | #define ERROR_LOG \ 49 | logger(__FILE__, __func__, __LINE__,L_ERROR_EXIT).stream() 50 | -------------------------------------------------------------------------------- /Shared/LogFile.cpp: -------------------------------------------------------------------------------- 1 | #include "LogFile.h" 2 | #include 3 | #include 4 | #include 5 | 6 | /* FILE* fp_; 7 | char buffer_[64 * 1024]; 8 | off_t writtenBytes_; 9 | */ 10 | AppendFile::AppendFile(const char* filename) 11 | { 12 | fp_ = fopen(filename, "a"); 13 | assert(fp_); 14 | } 15 | 16 | AppendFile::~AppendFile() 17 | { 18 | // if (fp_) { 19 | ::fflush(fp_); 20 | ::fclose(fp_); 21 | // } 22 | } 23 | 24 | void AppendFile::append(const char* logline, size_t len) 25 | { 26 | writtenBytes_ += len; 27 | ::fwrite(logline, len, 1, fp_); 28 | } 29 | 30 | void AppendFile::flush() 31 | { 32 | ::fflush(fp_); 33 | } 34 | 35 | LogFile::LogFile(const std::string& basename, 36 | off_t rollSize, 37 | bool threadSafe, 38 | int flushInterval, 39 | int checkEveryN) 40 | : basename_(basename) 41 | , rollSize_(rollSize) 42 | , flushInterval_(flushInterval) 43 | , checkEveryN_(checkEveryN) 44 | , count_(0) 45 | , muti_threads(threadSafe) 46 | // mutex_(threadSafe ? new MutexLock : NULL), 47 | , startOfPeriod_(0) 48 | , lastRoll_(0) 49 | , lastFlush_(0) 50 | { 51 | assert(basename.find('/') == std::string::npos); 52 | rollFile(); 53 | } 54 | 55 | void LogFile::append(std::string&& buf) 56 | { 57 | if (muti_threads) { 58 | std::lock_guard lock_guard(mutex_); 59 | append_unlocked(std::move(buf)); 60 | } else { 61 | append_unlocked(std::move(buf)); 62 | } 63 | } 64 | 65 | void LogFile::flush() 66 | { 67 | if (muti_threads) { 68 | std::lock_guard lock_guard(mutex_); 69 | file_->flush(); 70 | } else { 71 | file_->flush(); 72 | } 73 | } 74 | 75 | void LogFile::append_unlocked(std::string&& buf) 76 | { 77 | file_->append(buf.data(), buf.length()); 78 | 79 | if (file_->writtenBytes() > rollSize_) { 80 | rollFile(); 81 | } else { 82 | ++count_; 83 | if (count_ >= checkEveryN_) { 84 | count_ = 0; 85 | time_t now = ::time(NULL); 86 | if (now - lastFlush_ > flushInterval_) { 87 | lastFlush_ = now; 88 | file_->flush(); 89 | } 90 | } 91 | } 92 | } 93 | 94 | bool LogFile::rollFile() 95 | { 96 | time_t now = 0; 97 | std::string filename = getLogFileName(basename_, &now); 98 | time_t start = now / kRollPerSeconds_ * kRollPerSeconds_; 99 | 100 | if (now > lastRoll_) { 101 | lastRoll_ = now; 102 | lastFlush_ = now; 103 | startOfPeriod_ = start; 104 | file_.reset(new AppendFile(filename.c_str())); 105 | return true; 106 | } 107 | return false; 108 | } 109 | 110 | std::string LogFile::getLogFileName(const std::string& basename, time_t* now) 111 | { 112 | std::string filename; 113 | filename.reserve(basename.size() + 64); 114 | filename = basename; 115 | 116 | char timebuf[32]; 117 | struct tm tm; 118 | *now = time(NULL); 119 | #ifdef _WIN32 120 | gmtime_s(&tm, now); 121 | #else 122 | gmtime_r(now, &tm); // FIXME: localtime_r ? 123 | 124 | #endif 125 | strftime(timebuf, sizeof timebuf, ".%Y%m%d-%H%M%S", &tm); 126 | filename += timebuf; 127 | 128 | /*filename += ProcessInfo::hostname(); 129 | 130 | char pidbuf[32]; 131 | snprintf(pidbuf, sizeof pidbuf, ".%d", ProcessInfo::pid()); 132 | filename += pidbuf; 133 | */ 134 | filename += ".log"; 135 | 136 | return filename; 137 | } 138 | -------------------------------------------------------------------------------- /Shared/LogFile.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | #include 6 | class AppendFile { 7 | public: 8 | explicit AppendFile(const char* filename); 9 | 10 | ~AppendFile(); 11 | 12 | void append(const char* logline, size_t len); 13 | 14 | void flush(); 15 | 16 | off_t writtenBytes() const { return writtenBytes_; } 17 | 18 | private: 19 | FILE* fp_ { nullptr }; 20 | off_t writtenBytes_ { 0 }; 21 | }; 22 | class LogFile { 23 | public: 24 | LogFile(const std::string& basename, 25 | off_t rollSize, 26 | bool threadSafe = true, 27 | int flushInterval = 3, 28 | int checkEveryN = 1024); 29 | ~LogFile() = default; 30 | 31 | void append(std::string&& buf); 32 | void flush(); 33 | bool rollFile(); 34 | 35 | private: 36 | void append_unlocked(std::string&&); 37 | 38 | static std::string getLogFileName(const std::string& basename, time_t* now); 39 | 40 | const std::string basename_; 41 | const off_t rollSize_; 42 | const int flushInterval_; 43 | const int checkEveryN_; 44 | 45 | int count_; 46 | bool muti_threads; 47 | std::mutex mutex_; 48 | time_t startOfPeriod_; 49 | time_t lastRoll_; 50 | time_t lastFlush_; 51 | std::unique_ptr file_; 52 | 53 | const static int kRollPerSeconds_ = 60 * 60 * 24; 54 | }; -------------------------------------------------------------------------------- /Shared/Version.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define OVERPLUS_VER_MAJOR 1 4 | #define OVERPLUS_VER_MINOR 0 5 | #define OVERPLUS_VER_PATCH 5 6 | 7 | #define OVERPLUS_VERSION (OVERPLUS_VER_MAJOR * 10000 + OVERPLUS_VER_MINOR * 100 + OVERPLUS_VER_PATCH) 8 | const char* OVERPLUS_VERSION_STR ="v1.0.5"; 9 | 10 | -------------------------------------------------------------------------------- /asset/cert.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFDDCCAvQCAQEwDQYJKoZIhvcNAQEFBQAwTDELMAkGA1UEBhMCVVMxEzARBgNV 3 | BAgTCkNhbGlmb3JuaWExEjAQBgNVBAoTCW5ncm9rLmNvbTEUMBIGA1UEAxQLKi5u 4 | Z3Jvay5jb20wHhcNMTMwNjAzMDQyMzAwWhcNMjMwNjAxMDQyMzAwWjBMMQswCQYD 5 | VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEChMJbmdyb2suY29t 6 | MRQwEgYDVQQDFAsqLm5ncm9rLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC 7 | AgoCggIBANouXC6wJacPIFYEiKIGGhliShxsy181LJ064ZTD3ARMTi9ONOsRkHXT 8 | oM0CV1ZNDniPp3JILiGL52FPp0hdJegQ+pzz/gDjX0h9MCWexM4sqLpY0TfEqk9l 9 | HWKPJg+ODK8wa1/wuQOK++m/XY3wcbZgLj23EHcGybl/xGJkaeaqGNvrJo/4RgSL 10 | /jTpp0dDWKUmKBpTH4PuD6bkl8qKQAP1eul6ZS+Gr/llFYJWqwgd3SD3Wh/61Y4n 11 | 6sDusem2NQltnlFwe5VS1LfopVtO3e2N5fveEUdbg2YDs7evNYVK+1i0kjo0dD3D 12 | tbmhe13OQ/baufxcHc1xjtGsJ57ws8nePL4eo+vFuD5Qjc9ESfzunSem0hC5AJgF 13 | rsWzIYsr7UsiuKG4gQdD17L+DogVCqRcdYXugrPEJONi8YG3eRRP/Q9klX4PWy7O 14 | Zj7KX/xBrvS5GeDNqrDVsPL0W24FJ//Sus7BSF0fc5DSYqtD4Zgf1GRi0+xSa3m9 15 | AgJwWOHm6iC0mHwddozPLd+CP/M7PYzPRDiWY/ehoCFiqRAglYO0mhKyJvoTOjq6 16 | TBYYp/8LcbqfqUkI6DSnZ2fNdXKDyirMSOT4K1ug1MHaq8obucLqx+5Hie5d6CKR 17 | gnN15sJwGKIy27fg6LAzi1GWuCzZ1kHqjuQh3oVfuBKnFfsdLUnFAgMBAAEwDQYJ 18 | KoZIhvcNAQEFBQADggIBADael2+SR8LC5xNCBcu4eqmil30Whb7qqr16EHU/MSQb 19 | nmFMxBqTa2B8RDZIIkb7LukH8rsAdU8Bzkc2yRdjoAfEMMcJA/fMpwuaXI5cuaKV 20 | idZFpNUyR+K5UG/CntcCvwzZp4//g+LVK9qPDZ2BJmA/PMR6OphRwRwG+ruSLUCi 21 | ywgFFhNlPMpPZ49vsFm/Q0A4JmLpZXaARt53zNbNiHT1FgTP/9L1HIpkbfoFQT6R 22 | pB/VYe8O+GmrwaL3l+L8aO07JRM+u0OKNPxOgxWgE7UngiWulXpnIHAXYxTVMQ8V 23 | 1IYutv9bYJ/+TAy9Kxzc4Q33K+5qvS58GCAjOF10NnHkiTQPbhUWISoj21eZJ1hb 24 | Z4BbC5z/Y8zNbMao0ACF0QmlnXlt0YrkjkcZQuqerzpC6YMw32vsemREc55X9UQM 25 | mVbnDah4xyPM+yNIg/uKHVTYJ6+4TttWEp1JGRaP2EOYr0yGe4XFFrKIPH2DlApM 26 | nNfqR+Mfx9WZS3n6FuWPGWUDBye2fdxOjW9Jwc/+JDR3BsS65LlzPrs6C8TcNnnD 27 | EdWJ4klYzWkuSdUiV1EXbsB1sSIKmUud2f4vJuOqlBsgS8/mTxjk123uXaN9zaN6 28 | A9cMWQI2MbobK0HErkk0QyNTtVTKumEEko/c2ktsn8lrdJcKqCeQ9EKwp6r0Lmtp 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /asset/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKAIBAAKCAgEA2i5cLrAlpw8gVgSIogYaGWJKHGzLXzUsnTrhlMPcBExOL040 3 | 6xGQddOgzQJXVk0OeI+nckguIYvnYU+nSF0l6BD6nPP+AONfSH0wJZ7EziyouljR 4 | N8SqT2UdYo8mD44MrzBrX/C5A4r76b9djfBxtmAuPbcQdwbJuX/EYmRp5qoY2+sm 5 | j/hGBIv+NOmnR0NYpSYoGlMfg+4PpuSXyopAA/V66XplL4av+WUVglarCB3dIPda 6 | H/rVjifqwO6x6bY1CW2eUXB7lVLUt+ilW07d7Y3l+94RR1uDZgOzt681hUr7WLSS 7 | OjR0PcO1uaF7Xc5D9tq5/FwdzXGO0awnnvCzyd48vh6j68W4PlCNz0RJ/O6dJ6bS 8 | ELkAmAWuxbMhiyvtSyK4obiBB0PXsv4OiBUKpFx1he6Cs8Qk42Lxgbd5FE/9D2SV 9 | fg9bLs5mPspf/EGu9LkZ4M2qsNWw8vRbbgUn/9K6zsFIXR9zkNJiq0PhmB/UZGLT 10 | 7FJreb0CAnBY4ebqILSYfB12jM8t34I/8zs9jM9EOJZj96GgIWKpECCVg7SaErIm 11 | +hM6OrpMFhin/wtxup+pSQjoNKdnZ811coPKKsxI5PgrW6DUwdqryhu5wurH7keJ 12 | 7l3oIpGCc3XmwnAYojLbt+DosDOLUZa4LNnWQeqO5CHehV+4EqcV+x0tScUCAwEA 13 | AQKCAgBb6Ru8L0gtUBn3IoHMf3WPK/C8eLhTqzrYIW3WFYwh42MsWm3AeO26NSSQ 14 | OGRCXsOx1hJb+jw0tZMLU1rNCTBmyoBIjiB6j04cY2Bc+L0/fWC236ODMr3sJFR0 15 | qIkIFHcTdfpFuEq4S1xD4/GtUZUVlv7j0LKG8b0Y/9HjARn7qbw/KJheHeChGbhE 16 | 4gkt5Bj7uU87h7jHAwpk6/dlw0ekY00b/guSMdL/5K1i8s+p46q7sHeu8SP1dqtW 17 | Cze3lKJTDnKbLB9jkDk8IC1IgbjL0fMIX0w4Gz0HRJf40T5ioGuxup+/FUnCmyd6 18 | w6QMqE/JNesTfFqxqRzZBwTJ1+xkXkSFmFhZ9McfxmEtozau0LH6TtkGmHvfmn6/ 19 | I8ImlIRyHGOUVHsEAR8c7aD9dag0LbXmjyolfin8Dow130/G3hTXYnDETx66k1RT 20 | Bh6kDEYvuxvFqkXYKRAu5u11vQA9udYqN4i+htQ2OOYOEKNX8Kxy562rXgd85MyT 21 | eTL4Dmh/6J7VtWG4nN2G8pnj5Lg99Q8fzEFQmFLFcK0kJwHu04pJaqrHL+uHHd5T 22 | KOfPAr0pUbeu2sVd9chgDhADUE1/8YPhSCsHudM4No4tDCZVEqKJ1pGLCpUw3H1v 23 | h0aA/B582p2CGFpGoInKfFKvUbYt2neDZZQygmYCUHywqNxQAQKCAQEA/H0MCJYV 24 | VRRLufZkz3fpC+wT+coqbguwrT2bpVBuNdvyDKYIkbcSDtIVNsO/1p1/CGormDll 25 | k1jNFhR+lqBB8QQfb1ZetPohxzPNncPiJypb9Uivu7GG1MNUHJUecFt8uITcY93s 26 | UgRBVsCpLgUP7SF6m1Mpu2BaXQ/sEM8dYCSmt4Fax7LW00cIpLleZmCXoc/jsTUM 27 | ixYTM5r5UWXBhl196Lfu+WjTmoxGgFeTwF8x1wuo7mQvPfripvUX/bjGR4o5JQOw 28 | 1I0XK13yrhFkEAIV61nHBGiyhmJShPaSI2uDudx0ZkfuUIM1UFvqeqWsjATaLmOW 29 | X7lE0qNg75lsRQKCAQEA3Tcqgviss9a4Owr0BagleObUnTMFzk8bgpemNdzi47eG 30 | 7C572Wx8Gs6XgNbO28lpE0PD7KU/Rvt36wP9mzGiP0M3Zr4h81AfLvftoMMs6ytk 31 | wHJOdtPUCZqvnblvKOI409nGnoUD+3nfH/qqHk4ZL3fn4vHbxwModZfOXRrH2hJr 32 | EVqAnnexi9S0JbJJxdcSlBMI5vQMKGLCj52C2mfcF+XRMuF88bwfJ7Xcb3yhyv8i 33 | pXHHZZ9QKE7eG76UO+EGKz8gn3wWKuh5HXp3HoyKidgzXtPIbu8fd4UzXl/BTMFc 34 | jWAG0Ycsm5dTobQ0Yo0Xeju5Hs5QCtItY7OAFsH/gQKCAQBBCTC9UXNjO9wZpYbo 35 | DdoAkSnAELwHJom2xgS+e044H1Rkv6u7ZO2I1cJTHe7fKChdkYNzLW2lm50QD+1f 36 | fR4fJ9G1CwlQEpH6zrQq7Bbnwbh4IOXrMdoqGbojtqFljZs9qDNgofxKUABIiU3K 37 | pdEpYpNDSROZyULdb8l9tuu5JRewcuhgQgel2kk2rOzM8Bp+up7KuYBmnyQJCeUo 38 | e05y/sf81sv+gGrpBzLtwiEzzxF2c/FqnnGwxFv3Z3BrkVm5ebgoeZ/l0AXkzMlC 39 | 3wXoPbFJsxFZaGJ7zP22dBDGgN4oVMnCwsp3AKUN8u8d8mjUlDdi9ZH5TC6XFzBT 40 | 5zAFAoIBAFCjlXmc0MfV096iBYYyX0aNTp/nQ4yLRcn7IfmshYDhG+vonfkKFMto 41 | 1819gHaaGxWMtFUFf+WOMY6YK9Bw7WYGSKHJWXLqmBN1CUh7HVq0vMtyX6vtV/QQ 42 | UUg7movau0BuuHp8npEDQhTUOUNG0ON+4CbYZ3dKbWtAZVeHNacG48S1qwEZPL1u 43 | UiUTstTNq9YSgkI+YFgweCAGGPcouRB1FCdqDzPHkcvV/X8efZQUITsSGM+wnXW0 44 | Gj8e38ZcJvWI04mPoD0P9WaLh/S44p+REljU9tGJlXzqL2mNmlcyfVyDzrh+gAJP 45 | zYq6uAXczNwf/UF/j6oCJ82aV2z0VwECggEBAJga2Kb1ovNLMQ8Vum1tE8BOku9D 46 | hTVpz+sFugZuY5Dzl9sPGT3UXGjZZ8pGEGpLPQK2YP5yc+ehCng3ePtXV0sPUR5W 47 | 17jibM2JYFkR9RJCCA8c++2OwxZTVJcSDUkHG7cpl5QLbzOJAWG78EziiNqZEyC0 48 | RsYKPad6e0enYtPvYlnCYr77Y4KN6mU5Ot3OzqGeP4++dUMznL21vb2wmO6xHLR0 49 | Mwa6w7fIXYNAOuCk0GMeYzSHZT7ZYytUUhiiTVbHZuazJR6JyR7yf3dnjkPfRVHe 50 | qLsKIVNbyWayop61fNZWfN4FgLMmUDzCNNDz9sFX09Jm1QaEo+Dun7Dw8Ss= 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /asset/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyanrch/overplus/765a6363788340685bf0b609bf41a27a55b4bbdb/asset/flow.png -------------------------------------------------------------------------------- /asset/flow.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | Client -> proxy_server: setup tcp connection 3 | Client -> proxy_server: TLS handshake (client hello) 4 | proxy_server --> Client: TLS handshake (server hello certificate key exchage) 5 | Client ->proxy_server:trojan request(password destination host) 6 | proxy_server -> remote_server: setup tcp connection with destination 7 | Client->proxy_server:upstream payload 8 | proxy_server->remote_server:upstream payload 9 | proxy_server<-remote_server:downstream payload 10 | Client<-proxy_server:downstream payload 11 | 12 | @enduml -------------------------------------------------------------------------------- /asset/windows_socks5_proxy.md: -------------------------------------------------------------------------------- 1 | 打开注册表编辑器,路径输入:计算机\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings里面有一项 ProxyServer,修改值为 socks://HOST:PORT 即可。注意这里不是 socks5,是 socks,改完之后类似于浏览器之类的软件就会走 Socks5 代理了。 2 | 3 | ProxyEnable为1,即为开启状态才生效 -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - stage: Build 3 | jobs: 4 | - job: Linux 5 | pool: 6 | vmImage: ubuntu-latest 7 | container: 8 | image: trojangfw/centos-build:latest 9 | steps: 10 | - script: | 11 | set -euo pipefail 12 | cmake -DBoost_USE_STATIC_LIBS=ON . 13 | make 14 | strip -s overplus 15 | - publish: $(System.DefaultWorkingDirectory)/overplus 16 | artifact: LinuxBinary 17 | - stage: Package 18 | jobs: 19 | - job: Linux 20 | pool: 21 | vmImage: ubuntu-latest 22 | steps: 23 | - download: current 24 | artifact: LinuxBinary 25 | - script: | 26 | set -euo pipefail 27 | BINARY="$PIPELINE_WORKSPACE/LinuxBinary/overplus" 28 | chmod +x "$BINARY" 29 | mkdir overplus 30 | cp "$BINARY" overplus/overplus 31 | cp -r ConfigTemplate overplus 32 | tar cf overplus-linux-amd64.tar overplus 33 | xz overplus-linux-amd64.tar 34 | env: 35 | PIPELINE_WORKSPACE: $(Pipeline.Workspace) 36 | - publish: $(System.DefaultWorkingDirectory)/overplus-linux-amd64.tar.xz 37 | artifact: LinuxRelease 38 | -------------------------------------------------------------------------------- /buildFileEnv: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | LABEL Description="Build environment" 3 | 4 | ENV HOME /root 5 | 6 | SHELL ["/bin/bash", "-c"] 7 | 8 | RUN apt-get update && apt-get -y --no-install-recommends install \ 9 | build-essential \ 10 | openssl\ 11 | libssl-dev \ 12 | cmake \ 13 | wget 14 | 15 | # Let us add some heavy dependency 16 | RUN cd ${HOME} && \ 17 | wget --no-check-certificate --quiet \ 18 | https://boostorg.jfrog.io/artifactory/main/release/1.73.0/source/boost_1_73_0.tar.gz && \ 19 | tar xzf ./boost_1_73_0.tar.gz && \ 20 | cd ./boost_1_73_0 && \ 21 | ./bootstrap.sh && \ 22 | ./b2 -j$(nproc) --with-system --with-program_options variant=release link=static threading=multi runtime-link=shared install && \ 23 | cd .. && \ 24 | rm -rf ./boost_1_73 -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | #SERVER_NAME='' 4 | #SERVER_CERT="/etc/overplus/$SERVER_NAME.crt" 5 | #SERVER_KEY="/etc/overplus/$SERVER_NAME.crt" 6 | VERSION="v1.0.4" 7 | blue(){ 8 | echo -e "\033[34m\033[01m$1\033[0m" 9 | } 10 | green(){ 11 | echo -e "\033[32m\033[01m$1\033[0m" 12 | } 13 | red(){ 14 | echo -e "\033[31m\033[01m$1\033[0m" 15 | } 16 | 17 | if [[ -f /etc/redhat-release ]]; then 18 | release="centos" 19 | systemPackage="yum" 20 | systempwd="/usr/lib/systemd/system/" 21 | elif cat /etc/issue | grep -Eqi "debian"; then 22 | release="debian" 23 | systemPackage="apt-get" 24 | systempwd="/lib/systemd/system/" 25 | elif cat /etc/issue | grep -Eqi "ubuntu"; then 26 | release="ubuntu" 27 | systemPackage="apt-get" 28 | systempwd="/lib/systemd/system/" 29 | elif cat /etc/issue | grep -Eqi "centos|red hat|redhat"; then 30 | release="centos" 31 | systemPackage="yum" 32 | systempwd="/usr/lib/systemd/system/" 33 | elif cat /proc/version | grep -Eqi "debian"; then 34 | release="debian" 35 | systemPackage="apt-get" 36 | systempwd="/lib/systemd/system/" 37 | elif cat /proc/version | grep -Eqi "ubuntu"; then 38 | release="ubuntu" 39 | systemPackage="apt-get" 40 | systempwd="/lib/systemd/system/" 41 | elif cat /proc/version | grep -Eqi "centos|red hat|redhat"; then 42 | release="centos" 43 | systemPackage="yum" 44 | systempwd="/usr/lib/systemd/system/" 45 | fi 46 | 47 | 48 | function generate_certifiate(){ 49 | # Install the latest version of easy-rsa from source, if not already installed. 50 | if [ ! -d "/etc/overplus/easy-rsa" ]; then 51 | local version="3.1.1" 52 | wget -O ~/easy-rsa.tgz https://github.com/OpenVPN/easy-rsa/releases/download/v${version}/EasyRSA-${version}.tgz 53 | mkdir -p /etc/overplus/easy-rsa 54 | tar xzf ~/easy-rsa.tgz --strip-components=1 --directory /etc/overplus/easy-rsa 55 | rm -f ~/easy-rsa.tgz 56 | cd /etc/overplus/easy-rsa/ || return 57 | 58 | # Generate a random, alphanumeric identifier of 16 characters for CN and one for server name 59 | SERVER_CN="cn_$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)" 60 | echo "$SERVER_CN" >SERVER_CN_GENERATED 61 | SERVER_NAME="server_$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)" 62 | echo "$SERVER_NAME" >SERVER_NAME_GENERATED 63 | # Create the PKI, set up the CA, the DH params and the server certificate 64 | ./easyrsa --batch init-pki 65 | ./easyrsa --batch build-ca nopass 66 | ./easyrsa --batch --days=3650 build-server-full "$SERVER_NAME" nopass 67 | ./easyrsa --batch --days=3650 gen-crl 68 | else 69 | # If easy-rsa is already installed, grab the generated SERVER_NAME 70 | # for client configs 71 | cd /etc/overplus/easy-rsa/ || return 72 | SERVER_NAME=$(cat SERVER_NAME_GENERATED) 73 | fi 74 | # Move all the generated files 75 | cp pki/ca.crt pki/private/ca.key "pki/issued/$SERVER_NAME.crt" "pki/private/$SERVER_NAME.key" /etc/overplus/easy-rsa/pki/crl.pem /etc/overplus 76 | # Make cert revocation list readable for non-root 77 | chmod 644 /etc/overplus/crl.pem 78 | } 79 | function install_overplus(){ 80 | $systemPackage -y install openssl 81 | $systemPackage -y install xz-utils wget unzip zip curl tar 82 | Port443=`netstat -tlpn | awk -F '[: ]+' '$1=="tcp"{print $5}' | grep -w 443` 83 | if [ -n "$Port443" ]; then 84 | process443=`netstat -tlpn | awk -F '[: ]+' '$5=="443"{print $9}'` 85 | red "=============================================================" 86 | red "检测到443端口被占用,占用进程为:${process443},本次安装结束" 87 | red "=============================================================" 88 | exit 1 89 | fi 90 | 91 | SOFTWARE_PACKAGE=https://github.com/xyanrch/overplus/releases/download/${VERSION}/LinuxRelease.zip 92 | #PORT_CHOICE=${PORT_CHOICE:-1} 93 | #PORT="443" 94 | 95 | 96 | green "=======================" 97 | 98 | blue "please input overplus user password:" 99 | green "=======================" 100 | read USER_PASSWORD 101 | 102 | blue "What port do you want Overplus to listen to?" 103 | echo " 1) Default(recommend): 443" 104 | echo " 2) Custom" 105 | until [[ $PORT_CHOICE =~ ^[1-2]$ ]]; do 106 | read -rp "Port choice [1-2]: " -e -i 1 PORT_CHOICE 107 | done 108 | case $PORT_CHOICE in 109 | 1) 110 | PORT="443" 111 | ;; 112 | 2) 113 | until [[ $PORT =~ ^[0-9]+$ ]] && [ "$PORT" -ge 1 ] && [ "$PORT" -le 65535 ]; do 114 | read -rp "Custom port [1-65535]: " -e -i 1194 PORT 115 | done 116 | ;; 117 | 3) 118 | esac 119 | generate_certifiate 120 | cd 121 | blue "download overplus package ..." 122 | wget $SOFTWARE_PACKAGE 123 | unzip LinuxRelease.zip >/dev/null 2>&1 124 | cd LinuxRelease 125 | tar -xvf overplus-linux-amd64.tar.xz >/dev/null 2>&1 126 | 127 | cp overplus/overplus /usr/bin/overplus 128 | cp overplus/ConfigTemplate/server.json /etc/overplus/server.json 129 | #cp /home/xx/overplus/ConfigTemplate/server.json /etc/overplus/server.json 130 | 131 | NAME=$(cat /etc/overplus/easy-rsa/SERVER_NAME_GENERATED) 132 | SERVER_CERT="/etc/overplus/${NAME}.crt" 133 | SERVER_KEY="/etc/overplus/${NAME}.key" 134 | sed -i "s/VAR_PORT/$PORT/" /etc/overplus/server.json 135 | sed -i "s/VAR_PASSWORD/$USER_PASSWORD/" /etc/overplus/server.json 136 | 137 | sed -i "s~VAR_SERVER_CERT~${SERVER_CERT}~" /etc/overplus/server.json 138 | sed -i "s~VAR_SERVER_KEY~$SERVER_KEY~" /etc/overplus/server.json 139 | 140 | # mkdir -p /var/overplus 141 | cp overplus/ConfigTemplate/overplus.service /etc/systemd/system/overplus.service 142 | 143 | cd 144 | if [ -f LinuxRelease.zip ]; then 145 | rm -rf LinuxRelease.zip 146 | fi 147 | if [ -d './LinuxRelease' ]; then 148 | rm -rf LinuxRelease 149 | fi 150 | 151 | chmod 664 /etc/systemd/system/overplus.service 152 | systemctl daemon-reload 153 | systemctl start overplus.service 154 | systemctl enable overplus.service 155 | 156 | green " ====================================" 157 | green "Overplus has sucessfully installed" 158 | green "Please run \"systemctl status overplus.service\" to check service status" 159 | green " ====================================" 160 | green "Your sever config is located :/etc/overplus/server.json" 161 | cat /etc/overplus/server.json 162 | 163 | 164 | 165 | } 166 | function remove_overplus(){ 167 | systemctl stop overplus 168 | systemctl disable overplus 169 | if [ -d /etc/overplus ]; then 170 | rm -rf /etc/overplus 171 | fi 172 | if [ -f /usr/bin/overplus ]; then 173 | rm -rf /usr/bin/overplus 174 | fi 175 | if [ -f /etc/systemd/system/overplus.service ]; then 176 | rm -rf /etc/systemd/system/overplus.service 177 | fi 178 | 179 | } 180 | 181 | start_menu(){ 182 | clear 183 | green " ====================================" 184 | green " Welcome to the overplus installer! " 185 | 186 | green " =======================================" 187 | echo 188 | green " 1. Install overplus" 189 | red " 2. Remove overplus" 190 | 191 | blue " 0. Exit" 192 | echo 193 | read -p "Please input number:" num 194 | case "$num" in 195 | 1) 196 | install_overplus 197 | ;; 198 | 2) 199 | remove_overplus 200 | ;; 201 | 0) 202 | exit 1 203 | ;; 204 | *) 205 | clear 206 | red "Please input correct number" 207 | sleep 1s 208 | start_menu 209 | ;; 210 | esac 211 | } 212 | 213 | start_menu 214 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overplus", 3 | "version-string": "1.0.2", 4 | "dependencies": [ 5 | "boost", 6 | "openssl", 7 | "qt5-base" 8 | ] 9 | } 10 | --------------------------------------------------------------------------------