// // Created by imper on 6/21/21. // #ifndef INET_COMM_INET_COMM_ #define INET_COMM_INET_COMM_ #include #include #include #include #include #include #include #include #include #include #include #include #define PRINT_PREFIX log_console::l_lock << log_console::l_localtime << l_location #define DEBUG_COLOR color::faint << color::yellow #define LOG (__detail__::_log_ << PRINT_PREFIX) #define ERR (__detail__::_err_ << PRINT_PREFIX) #define ENDENTLN color::reset << log_console::l_endent #define ERROR(message) (ERR << color::red << color::bold << "error" << color::reset << " : " << color::red << color::italic << message << color::reset << color::red << "." << ENDENTLN) #define DEFAULT_ERROR_HANDLER [](const char* str, size_t size, void* err_stream) { *static_cast(err_stream) << PRINT_PREFIX << color::red << str << ENDENTLN; return 0; } #include #include #include #include #include #include #include namespace inet { class input_stream { public: template static inline std::unique_ptr mkstream(Args&& ... args) { return std::move(std::make_unique(std::move(args)...)); } inline input_stream() noexcept: bio(nullptr) { } inline input_stream(BIO* bio) noexcept: bio(bio) { } inline input_stream(const std::string& file) : bio(BIO_new(BIO_s_file())) { BIO_read_filename(bio, file.c_str()); } inline input_stream(const std::vector& key) : bio(nullptr) { int pipe[2]; ::pipe(pipe); ::write(pipe[1], key.data(), key.size()); ::close(pipe[1]); bio = BIO_new_fd(pipe[0], BIO_CLOSE); } inline input_stream(int fd) : bio(BIO_new_fd(fd, BIO_CLOSE)) { } input_stream(const input_stream&) = delete; inline input_stream(input_stream&& stream) noexcept: bio(stream.bio) { stream.bio = nullptr; } inline operator BIO*() const { return bio; } inline ~input_stream() { BIO_free_all(bio); } private: BIO* bio; }; class loader { public: inline loader() = default; loader(const loader&) = delete; loader& operator=(const loader&) = delete; inline loader(loader&& another) = default; inline loader(std::unique_ptr cert, std::unique_ptr pkey) : cert(std::move(cert)), pkey(std::move(pkey)) { } inline X509* load_cert() { return PEM_read_bio_X509(*cert, nullptr, nullptr, nullptr); } inline EVP_PKEY* load_pkey() { return PEM_read_bio_PrivateKey(*pkey, nullptr, nullptr, nullptr); } inline operator bool() const { return cert && pkey; } inline ~loader() = default; private: std::unique_ptr cert = nullptr; std::unique_ptr pkey = nullptr; }; namespace __detail__ __attribute__((visibility("hidden"))) { static log_console _log_(stdout); static log_console _err_(stderr); inline static SSL_CTX* init_server_ctx(const SSL_METHOD* method = DTLS_server_method()) { SSL_CTX* ctx; OpenSSL_add_all_algorithms(); /* load & register all cryptos, etc. */ SSL_load_error_strings(); /* load all error messages */ ctx = SSL_CTX_new(method); /* create new context from method */ if (ctx == nullptr) { ERR_print_errors_cb(DEFAULT_ERROR_HANDLER, new log_console(stderr)); return ctx; } SSL_CTX_set_cipher_list(ctx, "ALL:eNULL"); return ctx; } inline static SSL_CTX* init_client_ctx(const SSL_METHOD* method = DTLS_client_method()) { SSL_CTX* ctx; OpenSSL_add_all_algorithms(); /* load & register all cryptos, etc. */ SSL_load_error_strings(); /* load all error messages */ ctx = SSL_CTX_new(method); /* create new context from method */ if (ctx == nullptr) { ERR_print_errors_cb(DEFAULT_ERROR_HANDLER, new log_console(stderr)); return ctx; } SSL_CTX_set_cipher_list(ctx, "ALL:eNULL"); return ctx; } inline static int verify_callback(int, X509_STORE_CTX*) { return 1; } inline static bool load_certificates_server(SSL_CTX* ctx, X509* cert, EVP_PKEY* pkey) { if (SSL_CTX_set_default_verify_paths(ctx) != 1) ERR_print_errors_cb(DEFAULT_ERROR_HANDLER, new log_console(stderr)); /** Set the local certificate from CertFile **/ if (SSL_CTX_use_certificate(ctx, cert) <= 0) { ERR_print_errors_cb(DEFAULT_ERROR_HANDLER, new log_console(stderr)); return false; } /** Set the private key from KeyFile (may be the same as CertFile) **/ SSL_CTX_set_default_passwd_cb_userdata(ctx, (void*)"14880"); if (SSL_CTX_use_PrivateKey(ctx, pkey) <= 0) { ERR_print_errors_cb(DEFAULT_ERROR_HANDLER, new log_console(stderr)); return false; } /** Verify private key **/ if (!SSL_CTX_check_private_key(ctx)) { *new log_console(stderr) << log_console::l_localtime << l_location << color::red << "Private key does not match the public certificate" << ENDENTLN; return false; } /** Force the client-side have a certificate **/ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback); SSL_CTX_set_verify_depth(ctx, 4); return true; } inline static bool load_certificates_client(SSL_CTX* ctx, X509* cert, EVP_PKEY* pkey) { if (SSL_CTX_set_default_verify_paths(ctx) != 1) ERR_print_errors_cb(DEFAULT_ERROR_HANDLER, new log_console(stderr)); /** Set the local certificate from CertFile **/ if (SSL_CTX_use_certificate(ctx, cert) <= 0) { ERR_print_errors_cb(DEFAULT_ERROR_HANDLER, new log_console(stderr)); return false; } /** Set the private key from KeyFile (may be the same as CertFile) **/ SSL_CTX_set_default_passwd_cb_userdata(ctx, (void*)"14880"); if (SSL_CTX_use_PrivateKey(ctx, pkey) <= 0) { ERR_print_errors_cb(DEFAULT_ERROR_HANDLER, new log_console(stderr)); return false; } /** Verify private key **/ if (!SSL_CTX_check_private_key(ctx)) { *new log_console(stderr) << log_console::l_localtime << l_location << color::red << "Private key does not match the public certificate" << ENDENTLN; return false; } /** Force the client-side have a certificate **/ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback); SSL_CTX_set_verify_depth(ctx, 4); return true; } inline static void print_cert_info(SSL* ssl) { X509* cert; char* line; cert = SSL_get_peer_certificate(ssl); /* Get certificates (if available) */ if (cert) { auto log = LOG << "Server certificates:\n"; line = X509_NAME_oneline(X509_get_subject_name(cert), nullptr, 0); log << "Subject: " << color::blue << line; delete[] line; line = X509_NAME_oneline(X509_get_issuer_name(cert), nullptr, 0); log << "Issuer: " << color::blue << line << ENDENTLN; delete[] line; X509_free(cert); } else { ERR << "No certificates." << ENDENTLN; } } template inline void delete_buffer(T* buf) { delete[] buf; } } /// Generates ECDSA key. inline EVP_PKEY* generate_pkey(std::string& error, const char* ec_type_name = "secp521r1") { /* Allocate memory for the EVP_PKEY structure. */ auto* pkey = EVP_PKEY_new(); if (!pkey) { error = "Unable to create EVP_PKEY structure."; return nullptr; } /* Allocate memory for the EC_KEY structure. */ auto* ec_key = EC_KEY_new_by_curve_name(OBJ_txt2nid(ec_type_name)); if (!ec_key) { error = "Unable to create EC_KEY structure."; return nullptr; } /* For cert signing, we use the OPENSSL_EC_NAMED_CURVE flag*/ EC_KEY_set_asn1_flag(ec_key, OPENSSL_EC_NAMED_CURVE); if (!EC_KEY_generate_key(ec_key)) { error = "Unable to generate ECDSA key."; EC_KEY_free(ec_key); return nullptr; } if (!EVP_PKEY_assign_EC_KEY(pkey, ec_key)) { error = "Unable to generate ECDSA key."; EC_KEY_free(ec_key); EVP_PKEY_free(pkey); return nullptr; } return pkey; } /// Generates a self-signed x509 certificate. inline X509* generate_cert( std::string& error, EVP_PKEY* pkey, const std::string& country, const std::string& organization, const std::string& certificate_name, long serial_number = 1L, long valid_from = 0L, long valid_to = 31536000L) { /* Allocate memory for the X509 structure. */ auto* cert = X509_new(); if (!cert) { error = "Unable to create X509 structure."; return nullptr; } /* Set the serial number. */ ASN1_INTEGER_set(X509_get_serialNumber(cert), serial_number); /* This certificate is valid from until exactly . Default from now to one year. */ X509_gmtime_adj(X509_get_notBefore(cert), valid_from); X509_gmtime_adj(X509_get_notAfter(cert), valid_to); /* Set the public key for our certificate. */ X509_set_pubkey(cert, pkey); /* We want to copy the subject name to the issuer name. */ auto* name = X509_get_subject_name(cert); /* Set the country code and common name. */ X509_NAME_add_entry_by_txt( name, "C", MBSTRING_ASC, (unsigned char*)country.data(), country.size(), 1, 0 ); X509_NAME_add_entry_by_txt( name, "O", MBSTRING_ASC, (unsigned char*)organization.data(), organization.size(), 2, 0 ); X509_NAME_add_entry_by_txt( name, "CN", MBSTRING_ASC, (unsigned char*)certificate_name.data(), certificate_name.size(), 3, 0 ); /* Now set the issuer name. */ X509_set_issuer_name(cert, name); /* Actually sign the certificate with our key. */ if (!X509_sign(cert, pkey, EVP_sha256())) { error = "Error signing certificate."; X509_NAME_free(name); X509_free(cert); return nullptr; } return cert; } inline auto convert(EVP_PKEY* pkey) { char* buf = nullptr; size_t size; auto stream = open_memstream(&buf, &size); PEM_write_PrivateKey(stream, pkey, nullptr, nullptr, 0, nullptr, nullptr); ::fclose(stream); std::unique_ptr)> deleter( buf, &__detail__::delete_buffer ); return std::move(std::vector(buf, buf + size)); } inline auto convert(X509* cert) { char* buf = nullptr; size_t size; auto stream = open_memstream(&buf, &size); PEM_write_X509(stream, cert); ::fclose(stream); std::unique_ptr)> deleter( buf, &__detail__::delete_buffer ); return std::move(std::vector(buf, buf + size)); } inline bool write_certificate_to_disk( std::string& error, EVP_PKEY* pkey, X509* cert, const char* key_filename = "certificate.key", const char* x509_filename = "certificate.pem") { /* Open the PEM file for writing the key to disk. */ FILE* pkey_file = ::fopen(key_filename, "wb"); if (!pkey_file) { error = std::string("Unable to open file \"") + key_filename + "\" for writing."; return false; } /* Write the key to disk. */ bool ret = ::PEM_write_PrivateKey(pkey_file, pkey, nullptr, nullptr, 0, nullptr, nullptr); ::fclose(pkey_file); if (!ret) { error = "Unable to write private key to disk."; return false; } /* Open the PEM file for writing the certificate to disk. */ FILE* x509_file = ::fopen(x509_filename, "wb"); if (!x509_file) { error = std::string("Unable to open \"") + x509_filename + "\" for writing."; return false; } /* Write the certificate to disk. */ ret = ::PEM_write_X509(x509_file, cert); ::fclose(x509_file); if (!ret) { error = "Unable to write certificate to disk."; return false; } return true; } inline void cleanup_certificate(EVP_PKEY* key, X509* x509) { EVP_PKEY_free(key); X509_free(x509); } inline static void open_port_in_iptables(uint16_t port) { if (!geteuid()) { LOG << color::faint << DEBUG_COLOR << "executing opening ports command..." << ENDENTLN; std::string cmd("sudo iptables -A "); system((cmd + "INPUT -p tcp --dport " + std::to_string(port) + " -j ACCEPT").c_str()); system((cmd + "OUTPUT -p tcp --dport " + std::to_string(port) + " -j ACCEPT").c_str()); LOG << color::faint << DEBUG_COLOR << "executed." << ENDENTLN; } } inline static void close_port_in_iptables(uint16_t port) { if (!geteuid()) { LOG << color::faint << DEBUG_COLOR << "executing opening ports command..." << ENDENTLN; std::string cmd("sudo iptables -D "); system((cmd + "INPUT -p tcp --dport " + std::to_string(port) + " -j ACCEPT").c_str()); system((cmd + "OUTPUT -p tcp --dport " + std::to_string(port) + " -j ACCEPT").c_str()); LOG << color::faint << DEBUG_COLOR << "executed." << ENDENTLN; } } typedef struct ifinfo { struct sockaddr if_addr = {AF_INET}; struct sockaddr if_broadaddr = {AF_INET}; struct sockaddr if_dstaddr = {AF_INET}; struct sockaddr if_hwaddr = {0}; struct sockaddr if_netmask = {AF_INET}; struct ifmap if_map = { }; char if_name[IFNAMSIZ]{ }; short int if_flags = 0; int if_index = -1; int if_mtu = -1; } ifinfo, * pifinfo; inline static auto get_interface_information(const char* interface_name) { ifinfo result{ }; int fd = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); /* define the ifr_name - port name where network attached */ ::memcpy(result.if_name, interface_name, IFNAMSIZ - 1); /* address of the device using */ struct ifreq tmp{ }; ::memcpy(tmp.ifr_name, interface_name, IFNAMSIZ - 1); ::ioctl(fd, SIOCGIFADDR, &tmp); ::memcpy(result.if_addr.sa_data, tmp.ifr_addr.sa_data, 14); /* broadcast address for a device */ tmp = { }; ::memcpy(tmp.ifr_name, interface_name, IFNAMSIZ - 1); ::ioctl(fd, SIOCGIFBRDADDR, &tmp); ::memcpy(result.if_broadaddr.sa_data, tmp.ifr_broadaddr.sa_data, 14); /* destination address of a point-to-point device */ tmp = { }; ::memcpy(tmp.ifr_name, interface_name, IFNAMSIZ - 1); ::ioctl(fd, SIOCGIFDSTADDR, &tmp); ::memcpy(result.if_dstaddr.sa_data, tmp.ifr_dstaddr.sa_data, 14); /* active flag word of the device */ tmp = { }; ::memcpy(tmp.ifr_name, interface_name, IFNAMSIZ - 1); ::ioctl(fd, SIOCGIFFLAGS, &tmp); result.if_flags = tmp.ifr_flags; /* hardware (MAC) address */ tmp = { }; ::memcpy(tmp.ifr_name, interface_name, IFNAMSIZ - 1); ::ioctl(fd, SIOCGIFHWADDR, &tmp); ::memcpy(result.if_hwaddr.sa_data, tmp.ifr_hwaddr.sa_data, 14); /* index of the interface */ tmp = { }; ::memcpy(tmp.ifr_name, interface_name, IFNAMSIZ - 1); ::ioctl(fd, SIOCGIFINDEX, &tmp); result.if_index = tmp.ifr_ifindex; /* interface's hardware parameters */ tmp = { }; ::memcpy(tmp.ifr_name, interface_name, IFNAMSIZ - 1); ::ioctl(fd, SIOCGIFMAP, &tmp); result.if_map = tmp.ifr_map; /* MTU (Maximum Transfer Unit) of a device */ tmp = { }; ::memcpy(tmp.ifr_name, interface_name, IFNAMSIZ - 1); ::ioctl(fd, SIOCGIFMTU, &tmp); result.if_mtu = tmp.ifr_mtu; /* network mask for a device */ tmp = { }; ::memcpy(tmp.ifr_name, interface_name, IFNAMSIZ - 1); ::ioctl(fd, SIOCGIFNETMASK, &tmp); ::memcpy(result.if_netmask.sa_data, tmp.ifr_netmask.sa_data, 14); ::close(fd); return result; } inline static auto list_all_network_interfaces() { std::list result; struct ifaddrs* ifaddr, * ifa; if (::getifaddrs(&ifaddr) == -1) { ERROR("getifaddrs() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno)); return result; } for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { if (ifa->ifa_addr == nullptr || ifa->ifa_addr->sa_family != AF_PACKET) continue; result.push_back(ifa); } return result; } inline in_addr aton(const char* address) { in_addr ret{0}; ::inet_aton(address, &ret); return ret; } class suppress_signal { public: inline explicit suppress_signal(int _sig) : sig(_sig) { ::signal(_sig, sig_handle); } inline ~suppress_signal() { ::signal(this->sig, SIG_DFL); } inline static void enable_log(bool status = true) { if (status) _log_ << log_console::on; else _log_ << log_console::off; } inline static void enable_err(bool status = true) { if (status) _err_ << log_console::on; else _err_ << log_console::off; } private: int sig; inline static void sig_handle(int sig) { ERROR(::sigabbrev_np(sig) << " " << color::bold << ::sigdescr_np(sig)); } static log_console _log_; static log_console _err_; }; // # definition-begin suppress_signal log_console suppress_signal::_log_(stdout); log_console suppress_signal::_err_(stderr); // # definition-end suppress_signal class server; class inet_address { public: inline inet_address() = default; inline inet_address(const sockaddr_in& address) : address(address) { } inline inet_address(in_addr address, uint16_t port) : inet_address(sockaddr_in{AF_INET, ::htons(port), address}) { } inline inet_address(const std::string& address, uint16_t port) : inet_address(aton(address.c_str()), port) { } inline static inet_address& from_ipv4(const std::string& address, uint16_t default_port) { auto& addr = *new inet_address; addr.address.sin_family = AF_INET; size_t pos = address.find(':'); if (pos == std::string::npos) { addr.set_address(address.c_str()); addr.set_port(default_port); } else { addr.set_address(address.substr(0, pos).c_str()); if (++pos < address.size()) addr.set_port(std::stoul(address.substr(pos, address.size() - pos))); else addr.set_port(default_port); } return addr; } [[nodiscard]] inline const char* get_address() const { return ::inet_ntoa(this->address.sin_addr); } [[nodiscard]] inline uint16_t get_port() const { return ::ntohs(this->address.sin_port); } [[nodiscard]] inline sockaddr_in get_raw() const { return this->address; } inline void set_address(const char* address) { ::inet_aton(address, &this->address.sin_addr); } inline void set_port(uint16_t port) { this->address.sin_port = ::htons(port); } inline operator sockaddr_in() const { return this->operator const sockaddr_in&(); } inline operator const sockaddr_in&() const { return this->address; } private: friend class server; friend class inet_io; sockaddr_in address{AF_INET}; }; class inet_io; typedef bool (* client_process_function)(inet_io& io, const inet_address& address, server* server); class server { public: struct server_client_data { int socket; std::map* is_client_processing_ptr; SSL* ssl; inet_address address; server* serv; client_process_function process_function; int thread_id; int* threads_count; }; inline server( int max_clients, int timeo, const inet_address& address, client_process_function process_function, void* extra, loader&& crt); inline virtual bool run(bool wait); inline virtual void run_server_thread(bool wait_for_child_threads); inline static void wait_for_all_servers(); [[nodiscard]] inline int get_socket() const { return socket; } [[nodiscard]] inline int get_max_clients() const { return max_clients; } [[nodiscard]] inline int get_timeo() const { return timeo; } inline void set_timeo(int delay); [[nodiscard]] inline virtual const inet_address& get_address() const { return this->address; } [[nodiscard]] inline virtual client_process_function get_process_function() const { return process_function; } inline ~server(); void* extra; private: inline static void* server_thread(void* args); inline static void* process_client(void* args); inline void set_sockopts() { struct timeval timeout{timeo / 1000, (timeo % 1000) * 1000}; if (setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout) < 0) ERROR("setsockopt() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno) << color::reset << color::red << ". socket : " << socket << ". this = " << this); if (setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof timeout) < 0) ERROR("setsockopt() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno) << color::reset << color::red << ". socket : " << socket << ". this = " << this); } pthread_t main_thread = 0; int socket; SSL_CTX* ctx = nullptr; bool wait_for_child_threads = false; int max_clients, timeo; inet_address address; client_process_function process_function; static pthread_mutex_t client_process_mutex; static pthread_mutex_t clients_counter_mutex; static int server_threads; static pthread_mutex_t server_threads_counter_mutex; }; class inet_io { public: inline inet_io() : socket(-1), ssl(nullptr) { } inline inet_io(int socket) : socket(socket), ssl(nullptr) { } inline inet_io(SSL* ssl) : socket(ssl ? SSL_get_fd(ssl) : -1), ssl(ssl) { } inline virtual ssize_t write(const void* data, int size); inline virtual ssize_t read(void* data, size_t size); [[nodiscard]] inline int get_socket() const { return socket; } [[nodiscard]] inline SSL* get_ssl() const { return this->ssl; } inline virtual operator bool() const; protected: int socket; SSL* ssl; bool success = true; }; class client : public inet_io { public: inline client(int timeo, const inet_address& server_address, loader&& crt); [[nodiscard]] inline uint16_t get_port() const { return this->server_address.get_port(); } [[nodiscard]] inline const inet_address& get_server_address() const { return server_address; } [[nodiscard]] inline SSL_CTX* get_ctx() const { return this->ctx; } inline ~client(); private: inet_address server_address; SSL_CTX* ctx; }; // #definition-begin server int server::server_threads = 0; pthread_mutex_t server::client_process_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t server::clients_counter_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t server::server_threads_counter_mutex = PTHREAD_MUTEX_INITIALIZER; inline server::server( int max_clients, int timeo, const inet_address& address, client_process_function process_function, void* extra = nullptr, loader&& crt = { }) : max_clients(max_clients), timeo(timeo), address(address), process_function(process_function), extra(extra) { LOG << DEBUG_COLOR << "socket()..." << color::reset << "\n" << DEBUG_COLOR << "server address: " << color::blue_bg << color::black << address.get_address() << color::reset << DEBUG_COLOR << ";" << color::reset << "\n" << DEBUG_COLOR << "port = " << address.get_port() << DEBUG_COLOR << ";" << color::reset << "\n" << DEBUG_COLOR << "\tmax connections accept = " << max_clients << DEBUG_COLOR << ";" << color::reset << "\n" << DEBUG_COLOR << "this = " << this << DEBUG_COLOR << ";" << ENDENTLN; socket = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (socket < 0) { ERROR("socket() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno) << color::reset << color::red << ". socket : " << socket << ". this = " << this); throw std::runtime_error("socket() failed"); } LOG << DEBUG_COLOR << "bind()..." << color::reset << "\n" << DEBUG_COLOR << "this = " << this << DEBUG_COLOR << ";" << ENDENTLN; if (::bind(socket, (struct sockaddr*)&address.address, sizeof(address.address)) < 0) { ERROR("bind() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno) << color::reset << color::red << ". socket : " << socket << ". this = " << this); throw std::runtime_error("bind() failed"); } LOG << DEBUG_COLOR << "listening socket = " << color::reset << socket << DEBUG_COLOR << " ..." << ENDENTLN; if (listen(socket, max_clients) < 0) { ERROR("listen() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno) << color::reset << color::red << ". socket : " << socket << ". this = " << this); throw std::runtime_error("listen() failed"); } if (crt) { LOG << DEBUG_COLOR << "init_server_ctx()..." << ENDENTLN; ctx = __detail__::init_server_ctx(); LOG << DEBUG_COLOR << "load_certificates()..." << ENDENTLN; if (!__detail__::load_certificates_server(ctx, crt.load_cert(), crt.load_pkey())) { ERROR("load_certificates() returned " << false << color::red << ". socket : " << socket << ". this = " << this); } } LOG << DEBUG_COLOR << "success." << ENDENTLN; } inline bool server::run(bool wait) { int threads_count = 0, thread_id = 0; std::map is_client_processing; struct sockaddr_in address{ }; socklen_t addrlen = sizeof(address); int new_socket; while (!::fcntl(socket, F_GETFD)) { struct timeval timeout{timeo / 1000, (timeo % 1000) * 1000}; fd_set readfds; FD_ZERO(&readfds); FD_SET(socket, &readfds); if (::select(socket + 1, &readfds, nullptr, nullptr, &timeout) > 0 && !::fcntl(socket, F_GETFD)) { if (new_socket = ::accept(socket, (struct sockaddr*)&address, &addrlen); new_socket < 0) { ERROR("accept() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno) << color::reset << color::red << ". socket : " << socket << ". this = " << this); return errno == EBADF || errno == EINVAL; } LOG << DEBUG_COLOR << "+====== new connection ======/’" << color::reset << "\n" << DEBUG_COLOR << "| socket = " << color::reset << new_socket << DEBUG_COLOR << ";" << color::reset << "\n" << DEBUG_COLOR << "| address = " << color::blue_bg << color::black << ::inet_ntoa(address.sin_addr) << DEBUG_COLOR << ";" << color::reset << "\n" << DEBUG_COLOR << "| port = " << color::reset << ::ntohs(address.sin_port) << DEBUG_COLOR << ";" << color::reset << "\n\n" << DEBUG_COLOR << "+============================\\," << ENDENTLN; SSL* ssl = nullptr; if (ctx) { ssl = SSL_new(ctx); /** raw new SSL state with context **/ if (!ssl) { ERROR("SSL_new() returned " << color::italic << color::purple << "nullptr" << color::reset << color::red << ". socket : " << socket << ". this = " << this); return true; } if (!SSL_set_fd(ssl, new_socket)) /** set connection socket to SSL state **/ { ERR_print_errors_cb(DEFAULT_ERROR_HANDLER, &__detail__::_err_); return true; } if (ssl) { if (SSL_accept(ssl) <= 0) { ERROR("SSL_accept() failed. socket : " << socket << ". this = " << this); ERR_print_errors_cb(DEFAULT_ERROR_HANDLER, &__detail__::_err_); ssl = nullptr; return true; } } __detail__::print_cert_info(ssl); } auto* dat = new server_client_data{ }; dat->socket = new_socket; dat->ssl = ssl; dat->serv = const_cast(this); dat->address = inet_address(address); dat->is_client_processing_ptr = &is_client_processing; dat->thread_id = ++thread_id; ::pthread_mutex_lock(&clients_counter_mutex); dat->threads_count = &++threads_count; ::pthread_mutex_unlock(&clients_counter_mutex); dat->process_function = process_function; LOG << DEBUG_COLOR << "running client processing thread..." << ENDENTLN; pthread_t pthread; ::pthread_create(&pthread, nullptr, process_client, (void*)dat); ::pthread_detach(pthread); LOG << DEBUG_COLOR << "done." << ENDENTLN; } else ::usleep(10000); } while (wait && threads_count > 0) { ::usleep(10000); } return !::fcntl(socket, F_GETFD); } void server::set_timeo(int delay) { timeo = delay; set_sockopts(); } inline void* server::server_thread(void* args) { auto* server = static_cast(args); while (server->run(server->wait_for_child_threads)); ::pthread_mutex_lock(&server_threads_counter_mutex); --server::server_threads; ::pthread_mutex_unlock(&server_threads_counter_mutex); return nullptr; } inline void server::run_server_thread(bool wait_for_child_threads = false) { this->wait_for_child_threads = wait_for_child_threads; ::pthread_mutex_lock(&server_threads_counter_mutex); ++server::server_threads; ::pthread_mutex_unlock(&server_threads_counter_mutex); LOG << DEBUG_COLOR << "starting main server thread..." << ENDENTLN; ::pthread_create(&this->main_thread, nullptr, server_thread, static_cast(const_cast(this))); ::pthread_detach(this->main_thread); LOG << DEBUG_COLOR << "done." << ENDENTLN; } inline void server::wait_for_all_servers() { while (server::server_threads > 0) ::usleep(10000); } inline void* server::process_client(void* args) { auto& data = *static_cast(args); int client_socket = data.socket; LOG << DEBUG_COLOR << "socket : " << client_socket << ENDENTLN; if ((*data.is_client_processing_ptr)[client_socket]) return nullptr; ::pthread_mutex_lock(&client_process_mutex); (*data.is_client_processing_ptr)[client_socket] = true; ::pthread_mutex_unlock(&client_process_mutex); LOG << DEBUG_COLOR << "processing client..." << color::reset << "\n" << DEBUG_COLOR << "\tthread_no = " << data.thread_id << DEBUG_COLOR << "; this = " << data.serv << color::reset << "\n" << DEBUG_COLOR << "sock = " << data.socket << DEBUG_COLOR << "; ssl = " << data.ssl << ENDENTLN; auto io = (data.ssl ? inet_io(data.ssl) : inet_io(data.socket)); while (data.process_function(io, data.address, data.serv)); LOG << DEBUG_COLOR << "SSL_free()..." << ENDENTLN; SSL_free(data.ssl); LOG << color::faint << "job finished.\n\tthread_no = " << data.thread_id << ";" << ENDENTLN; ::pthread_mutex_lock(&client_process_mutex); (*data.is_client_processing_ptr)[client_socket] = false; ::pthread_mutex_unlock(&client_process_mutex); ::pthread_mutex_lock(&clients_counter_mutex); --*data.threads_count; ::pthread_mutex_unlock(&clients_counter_mutex); return nullptr; } inline server::~server() { LOG << DEBUG_COLOR << "destruction of server(); this = " << this << ENDENTLN; pthread_kill(this->main_thread, 0); ::close(this->socket); this->socket = -1; SSL_CTX_free(this->ctx); } // #definition-end server // #definition-begin inet_io inline ssize_t inet_io::write(const void* data, int size) { ssize_t wrote; this->success = true; if (this->ssl) { if ((wrote = SSL_write(this->ssl, data, size) <= 0)) { ERR << "this = " << this << ENDENTLN; ERR_print_errors_cb(DEFAULT_ERROR_HANDLER, &__detail__::_err_); this->success = false; } } else if ((wrote = ::send(this->socket, data, size, 0)) <= 0) { ERR << "this = " << this << ENDENTLN; ERROR("write() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno)); this->success = false; } return wrote; } inline ssize_t inet_io::read(void* data, size_t size) { this->success = true; ssize_t read; if (this->ssl) read = SSL_read(this->ssl, data, size); else read = ::recv(this->socket, data, size, 0); if (read <= 0) { ERR << "this = " << this << ENDENTLN; if (this->ssl) ERR_print_errors_cb(DEFAULT_ERROR_HANDLER, &__detail__::_err_); else ERROR("read() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno)); this->success = false; } return read; } inline inet_io::operator bool() const { return this->success; } // #definition-end inet_io // #definition-begin client inline client::client(int timeo, const inet_address& server_address, loader&& crt = { }) : server_address(server_address) { LOG << DEBUG_COLOR << "socket init..." << color::reset << "\n" << DEBUG_COLOR << "server address: " << color::blue_bg << color::black << server_address.get_address() << DEBUG_COLOR << ";" << color::reset << "\n" << DEBUG_COLOR << "port = " << server_address.get_port() << DEBUG_COLOR << ";" << ENDENTLN; socket = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (socket < 0) { ERROR("socket() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno) << color::reset << color::red << ". socket : " << socket << ". this = " << this); throw std::runtime_error("socket() failed"); } ctx = nullptr; if (crt) { LOG << DEBUG_COLOR << "initializing ctx..." << ENDENTLN; ctx = __detail__::init_client_ctx(); LOG << DEBUG_COLOR << "loading certificates..." << ENDENTLN; if (!__detail__::load_certificates_client(ctx, crt.load_cert(), crt.load_pkey())) { ERROR("load_certificates() returned " << false << color::red << ". this = " << this); SSL_CTX_free(ctx); ctx = nullptr; } } fd_set set{ }; FD_ZERO(&set); FD_SET(socket, &set); struct timeval timeout{timeo / 1000, (timeo % 1000) * 1000}; int opt; if ((opt = ::fcntl(socket, F_GETFL, nullptr)) < 0) { ERROR("fcntl() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno)); opt = 0; } if (::fcntl(socket, F_SETFL, opt | O_NONBLOCK) < 0) { ERROR("fcntl() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno)); throw std::runtime_error("fcntl() failed"); } LOG << DEBUG_COLOR << "connecting..." << ENDENTLN; if (::connect(socket, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) { if (errno != EINPROGRESS) { ERROR("connect() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno)); throw std::runtime_error("connect() failed"); } } if (::select(socket + 1, nullptr, &set, nullptr, &timeout) <= 0) { ERROR("select() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno)); throw std::runtime_error("select() failed"); } if ((opt = ::fcntl(socket, F_GETFL, nullptr)) < 0) { ERROR("fcntl() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno)); opt = 0; } if (::fcntl(socket, F_SETFL, opt & ~O_NONBLOCK) < 0) { ERROR("fcntl() failed " << ::strerrorname_np(errno) << " " << color::bold << ::strerrordesc_np(errno)); throw std::runtime_error("fcntl() failed"); } if (ctx) { if (!(ssl = SSL_new(ctx))) { ERROR("SSL_new() failed."); ERR_print_errors_cb(DEFAULT_ERROR_HANDLER, &__detail__::_err_); throw std::runtime_error("SSL_new() failed"); } SSL_set_fd(ssl, this->socket); LOG << DEBUG_COLOR << "SSL_connect()..." << ENDENTLN; if (SSL_connect(ssl) <= 0) { ERR << "this = " << this << ENDENTLN; ERR_print_errors_cb(DEFAULT_ERROR_HANDLER, &__detail__::_err_); ssl = nullptr; throw std::runtime_error("SSL_connect() failed"); } __detail__::print_cert_info(ssl); } LOG << DEBUG_COLOR << "success." << ENDENTLN; } inline client::~client() { LOG << DEBUG_COLOR << "destruction of client(); this = " << this << ENDENTLN; ::close(this->socket); SSL_free(this->ssl); } // #definition-end client } #endif // INET_COMM_INET_COMM_