// ============================================================================ // // = LIBRARY // ULib - c++ library // // = FILENAME // server.cpp // // = AUTHOR // Stefano Casazza // // ============================================================================ #include #include #include #include #include #include #include #include #include #include #include #ifdef _MSWINDOWS_ # include #else # include # include # include # ifdef HAVE_SCHED_GETCPU # include # endif # ifdef HAVE_LIBNUMA # include # endif #endif #ifdef U_STATIC_HANDLER_RPC # include #endif #ifdef U_STATIC_HANDLER_SHIB # include #endif #ifdef U_STATIC_HANDLER_ECHO # include #endif #ifdef U_STATIC_HANDLER_STREAM # include #endif #ifdef U_STATIC_HANDLER_NOCAT # include #endif #ifdef U_STATIC_HANDLER_NODOG # include #endif #ifdef U_STATIC_HANDLER_SOCKET # include #endif #ifdef U_STATIC_HANDLER_SCGI # include #endif #ifdef U_STATIC_HANDLER_FCGI # include #endif #ifdef U_STATIC_HANDLER_GEOIP # include #endif #ifdef U_STATIC_HANDLER_PROXY # include #endif #ifdef U_STATIC_HANDLER_SOAP # include #endif #ifdef U_STATIC_HANDLER_SSI # include #endif #ifdef U_STATIC_HANDLER_TSA # include #endif #ifdef U_STATIC_HANDLER_HTTP # include #endif int UServer_Base::rkids; int UServer_Base::timeoutMS; int UServer_Base::verify_mode; int UServer_Base::socket_flags; int UServer_Base::iAddressType; int UServer_Base::tcp_linger_set = -2; int UServer_Base::preforked_num_kids; bool UServer_Base::bssl; bool UServer_Base::bipc; bool UServer_Base::budp; bool UServer_Base::binsert; bool UServer_Base::flag_loop; bool UServer_Base::public_address; bool UServer_Base::monitoring_process; bool UServer_Base::set_tcp_keep_alive; bool UServer_Base::set_realtime_priority; bool UServer_Base::update_date; bool UServer_Base::update_date1; bool UServer_Base::update_date2; bool UServer_Base::update_date3; bool UServer_Base::called_from_handlerTime; long UServer_Base::last_time_email_crash; char UServer_Base::mod_name[2][32]; ULog* UServer_Base::log; ULog* UServer_Base::apache_like_log; char* UServer_Base::client_address; ULock* UServer_Base::lock_user1; ULock* UServer_Base::lock_user2; uint32_t UServer_Base::vplugin_size; uint32_t UServer_Base::nClientIndex; uint32_t UServer_Base::crash_count; uint32_t UServer_Base::client_address_len; uint32_t UServer_Base::document_root_size; uint32_t UServer_Base::num_client_threshold; uint32_t UServer_Base::min_size_for_sendfile; sigset_t UServer_Base::mask; UString* UServer_Base::host; UString* UServer_Base::server; UString* UServer_Base::as_user; UString* UServer_Base::dh_file; UString* UServer_Base::ca_file; UString* UServer_Base::auth_ip; UString* UServer_Base::ca_path; UString* UServer_Base::key_file; UString* UServer_Base::password; UString* UServer_Base::cert_file; UString* UServer_Base::name_sock; UString* UServer_Base::IP_address; UString* UServer_Base::cenvironment; UString* UServer_Base::senvironment; UString* UServer_Base::document_root; UString* UServer_Base::crashEmailAddress; USocket* UServer_Base::socket; USocket* UServer_Base::csocket; UProcess* UServer_Base::proc; UEventFd* UServer_Base::handler_other; UEventFd* UServer_Base::handler_inotify; UEventTime* UServer_Base::ptime; UUDPSocket* UServer_Base::udp_sock; const char* UServer_Base::document_root_ptr; unsigned int UServer_Base::port; USmtpClient* UServer_Base::emailClient; UFileConfig* UServer_Base::cfg; UServer_Base* UServer_Base::pthis; uint32_t UServer_Base::map_size; uint32_t UServer_Base::shared_data_add; UServer_Base::shared_data* UServer_Base::ptr_shared_data; uint32_t UServer_Base::shm_size; uint32_t UServer_Base::shm_data_add; UServer_Base::shm_data* UServer_Base::ptr_shm_data; UVector* UServer_Base::vplugin_name; UVector* UServer_Base::vplugin_name_static; UClientImage_Base* UServer_Base::vClientImage; UClientImage_Base* UServer_Base::pClientImage; UClientImage_Base* UServer_Base::eClientImage; UVector* UServer_Base::vplugin; UVector* UServer_Base::vplugin_static; UVector* UServer_Base::vlog; #ifdef USERVER_UDP vPFi UServer_Base::runDynamicPage_udp; #endif #ifdef U_WELCOME_SUPPORT UString* UServer_Base::msg_welcome; #endif #ifdef USE_LOAD_BALANCE UString* UServer_Base::ifname; unsigned char UServer_Base::loadavg_threshold = 45; // => 4.5 UVector* UServer_Base::vallow_cluster; #endif #ifdef U_ACL_SUPPORT UVector* UServer_Base::vallow_IP; #endif #ifdef U_RFC1918_SUPPORT bool UServer_Base::enable_rfc1918_filter; UVector* UServer_Base::vallow_IP_prv; #endif #ifdef DEBUG # ifdef USE_LIBEVENT # define U_WHICH "libevent" # elif defined(HAVE_EPOLL_WAIT) # define U_WHICH "epoll" # else # define U_WHICH "select" # endif # ifndef U_LOG_DISABLE long UServer_Base::last_event; # endif uint32_t UServer_Base::nread; uint32_t UServer_Base::max_depth; uint32_t UServer_Base::nread_again; uint64_t UServer_Base::stats_bytes; uint32_t UServer_Base::stats_connections; uint32_t UServer_Base::stats_simultaneous; uint32_t UServer_Base::wakeup_for_nothing; UTimeStat* UServer_Base::pstat; UString UServer_Base::getStats() { U_TRACE_NO_PARAM(0, "UServer_Base::getStats()") UString x(U_CAPACITY); x.snprintf(U_CONSTANT_TO_PARAM("%4u connections (%5.2f/sec), %3u max simultaneous, %4u %s (%5.2f/sec) - %v/sec"), UServer_Base::stats_connections, (float) UServer_Base::stats_connections / U_ONE_HOUR_IN_SECOND, UServer_Base::stats_simultaneous, UNotifier::nwatches, U_WHICH, (float) UNotifier::nwatches / U_ONE_HOUR_IN_SECOND, UStringExt::printSize(UServer_Base::stats_bytes).rep); U_RETURN_STRING(x); } class U_NO_EXPORT UTimeStat : public UEventTime { public: UTimeStat() : UEventTime(U_ONE_HOUR_IN_SECOND, 0L) { U_TRACE_CTOR(0, UTimeStat, "", 0) } virtual ~UTimeStat() U_DECL_FINAL { U_TRACE_DTOR(0, UTimeStat) } // define method VIRTUAL of class UEventTime virtual int handlerTime() U_DECL_FINAL { U_TRACE_NO_PARAM(0, "UTimeStat::handlerTime()") if (UServer_Base::stats_bytes) { U_DEBUG("%v", UServer_Base::getStats().rep) UServer_Base::stats_bytes = 0; } UNotifier::nwatches = UServer_Base::stats_connections = UServer_Base::stats_simultaneous = 0; U_RETURN(0); // monitoring } #if defined(DEBUG) && defined(U_STDCPP_ENABLE) const char* dump(bool _reset) const { return UEventTime::dump(_reset); } #endif private: U_DISALLOW_COPY_AND_ASSIGN(UTimeStat) }; #endif class U_NO_EXPORT UTimeoutConnection : public UEventTime { public: UTimeoutConnection() : UEventTime(UServer_Base::timeoutMS / 1000L, 0L) { U_TRACE_CTOR(0, UTimeoutConnection, "", 0) } virtual ~UTimeoutConnection() U_DECL_FINAL { U_TRACE_DTOR(0, UTimeoutConnection) } // define method VIRTUAL of class UEventTime virtual int handlerTime() U_DECL_FINAL { U_TRACE_NO_PARAM(0, "UTimeoutConnection::handlerTime()") // there are idle connection... (timeout) # if !defined(U_LOG_DISABLE) || (!defined(USE_LIBEVENT) && defined(HAVE_EPOLL_WAIT) && defined(DEBUG)) UServer_Base::called_from_handlerTime = true; # endif # ifndef U_LOG_DISABLE if (UServer_Base::isLog()) { # ifdef DEBUG long delta = (u_now->tv_sec - UServer_Base::last_event) - UTimeVal::tv_sec; if (delta >= 1 || delta <= -1) { U_SRV_LOG("handlerTime: server delta timeout exceed 1 sec: diff %ld sec", delta); } UServer_Base::last_event = u_now->tv_sec; # endif } # endif U_INTERNAL_DUMP("UNotifier::num_connection = %u UNotifier::min_connection = %u", UNotifier::num_connection, UNotifier::min_connection) if (UNotifier::num_connection > UNotifier::min_connection) UNotifier::callForAllEntryDynamic(UServer_Base::handlerTimeoutConnection); # if !defined(U_LOG_DISABLE) && defined(U_LINUX) && defined(ENABLE_THREAD) if (U_SRV_CNT_PARALLELIZATION) # endif UServer_Base::removeZombies(); U_RETURN(0); // monitoring } #if defined(DEBUG) && defined(U_STDCPP_ENABLE) const char* dump(bool _reset) const { return UEventTime::dump(_reset); } #endif private: U_DISALLOW_COPY_AND_ASSIGN(UTimeoutConnection) }; /** * The throttle data lets you set maximum byte rates on URLs or URL groups. You can optionally set a minimum rate too. * The format of the throttle data is very simple, should consist of a pattern, whitespace, and a number. The pattern * is a simple shell-style filename pattern, using ?*, or multiple such patterns separated by |. The numbers are byte * rates, specified in units of kbytes per second. If you want to set a minimum rate as well, use number-number. * * For example assuming we have a bandwith of 150 kB/s * * a) * 20-100 => limit total web usage to 2/3, but never go below 20 kB/s * b) *.jpg|*.gif 50 => limit images to 1/3 of our bandwith * c) *.mpg 20 => and movies to even less * * Throttling is implemented by checking each incoming URL filename against all of the patterns in the throttle data. * The server accumulates statistics on how much bandwidth each pattern has accounted for recently (via a rolling average). * If a URL matches a pattern that has been exceeding its specified limit, then the data returned is actually slowed down, * with pauses between each block. If that's not possible or if the bandwidth has gotten way larger than the limit, then * the server returns a special code */ #ifdef U_THROTTLING_SUPPORT bool UServer_Base::throttling_chk; UString* UServer_Base::throttling_mask; UServer_Base::uthrottling* UServer_Base::throttling_rec; URDBObjectHandler* UServer_Base::db_throttling; #define U_THROTTLE_TIME 2 // Time between updates of the throttle table's rolling averages class U_NO_EXPORT UBandWidthThrottling : public UEventTime { public: UBandWidthThrottling() : UEventTime(U_THROTTLE_TIME, 0L) { U_TRACE_CTOR(0, UBandWidthThrottling, "", 0) } virtual ~UBandWidthThrottling() U_DECL_FINAL { U_TRACE_DTOR(0, UBandWidthThrottling) } // SERVICES static int clearThrottling(UStringRep* key, UStringRep* data) { U_TRACE(0, "UBandWidthThrottling::clearThrottling(%p,%p)", key, data) if (key) { if (UServices::dosMatchWithOR(U_STRING_TO_PARAM(UServer_Base::pClientImage->uri), U_STRING_TO_PARAM(*key))) U_RETURN(4); // NB: call us later (after set record value from db) U_RETURN(1); } U_INTERNAL_DUMP("krate = %u min_limit = %u max_limit = %u num_sending = %u bytes_since_avg = %llu", UServer_Base::throttling_rec->krate, UServer_Base::throttling_rec->min_limit, UServer_Base::throttling_rec->max_limit, UServer_Base::throttling_rec->num_sending, UServer_Base::throttling_rec->bytes_since_avg) U_INTERNAL_ASSERT_MAJOR(UServer_Base::throttling_rec->num_sending, 0) UServer_Base::throttling_rec->num_sending--; // NB: db can have different pattern matching the same url... U_RETURN(1); } static int checkThrottling(UStringRep* key, UStringRep* data) { U_TRACE(0, "UBandWidthThrottling::checkThrottling(%p,%p)", key, data) if (key) { if (UServices::dosMatchWithOR(U_HTTP_URI_TO_PARAM, U_STRING_TO_PARAM(*key))) U_RETURN(4); // NB: call us later (after set record value from db)... U_RETURN(1); } U_INTERNAL_DUMP("krate = %u min_limit = %u max_limit = %u num_sending = %u bytes_since_avg = %llu", UServer_Base::throttling_rec->krate, UServer_Base::throttling_rec->min_limit, UServer_Base::throttling_rec->max_limit, UServer_Base::throttling_rec->num_sending, UServer_Base::throttling_rec->bytes_since_avg) if ( UServer_Base::throttling_rec->krate && (UServer_Base::throttling_rec->krate > (UServer_Base::throttling_rec->max_limit * 2) || // if we're way over the limit, don't even start... UServer_Base::throttling_rec->krate < UServer_Base::throttling_rec->min_limit)) // ...also don't start if we're under the minimum { UServer_Base::throttling_chk = false; U_RETURN(0); // stop } UServer_Base::throttling_rec->num_sending++; uint32_t l = UServer_Base::throttling_rec->max_limit / UServer_Base::throttling_rec->num_sending; UServer_Base::pClientImage->max_limit = (UServer_Base::pClientImage->max_limit == U_NOT_FOUND ? l : U_min(l, UServer_Base::pClientImage->max_limit)); l = UServer_Base::throttling_rec->min_limit; UServer_Base::pClientImage->min_limit = (UServer_Base::pClientImage->min_limit == U_NOT_FOUND ? l : U_max(l, UServer_Base::pClientImage->min_limit)); U_INTERNAL_DUMP("UServer_Base::pClientImage->min_limit = %u UServer_Base::pClientImage->max_limit = %u", UServer_Base::pClientImage->min_limit, UServer_Base::pClientImage->max_limit) // NB: db can have different pattern matching the same url... U_RETURN(1); } static int updateThrottling(UStringRep* key, UStringRep* data) { U_TRACE(0, "UBandWidthThrottling::updateThrottling(%p,%p)", key, data) if (key) U_RETURN(4); // NB: call us later (after set record value from db)... U_INTERNAL_DUMP("krate = %u min_limit = %u max_limit = %u num_sending = %u bytes_since_avg = %llu", UServer_Base::throttling_rec->krate, UServer_Base::throttling_rec->min_limit, UServer_Base::throttling_rec->max_limit, UServer_Base::throttling_rec->num_sending, UServer_Base::throttling_rec->bytes_since_avg) UServer_Base::throttling_rec->krate = ( 2 * UServer_Base::throttling_rec->krate + UServer_Base::throttling_rec->bytes_since_avg / (U_THROTTLE_TIME * 1024ULL)) / 3; UServer_Base::throttling_rec->bytes_since_avg = 0; U_INTERNAL_DUMP("krate = %u", UServer_Base::throttling_rec->krate) # ifndef U_LOG_DISABLE if (UServer_Base::isLog()) { if (UServer_Base::throttling_rec->num_sending) { if (UServer_Base::throttling_rec->krate > UServer_Base::throttling_rec->max_limit) { UServer_Base::log->log(U_CONSTANT_TO_PARAM("throttle %V: krate %u %sexceeding limit %u; %u sending"), UServer_Base::db_throttling->getKeyID().rep, UServer_Base::throttling_rec->krate, (UServer_Base::throttling_rec->krate > UServer_Base::throttling_rec->max_limit * 2 ? "greatly " : ""), UServer_Base::throttling_rec->max_limit, UServer_Base::throttling_rec->num_sending); } if (UServer_Base::throttling_rec->krate < UServer_Base::throttling_rec->min_limit) { UServer_Base::log->log(U_CONSTANT_TO_PARAM("throttle %V: krate %u lower than minimum %u; %u sending"), UServer_Base::db_throttling->getKeyID().rep, UServer_Base::throttling_rec->krate, UServer_Base::throttling_rec->min_limit, UServer_Base::throttling_rec->num_sending); } } } # endif U_RETURN(1); } static int updateSending(UStringRep* key, UStringRep* data) { U_TRACE(0, "UBandWidthThrottling::updateSending(%p,%p)", key, data) if (key) { if (UServices::dosMatchWithOR(U_HTTP_URI_TO_PARAM, U_STRING_TO_PARAM(*key))) U_RETURN(4); // NB: call us later (after set record value from db)... U_RETURN(1); } U_INTERNAL_DUMP("krate = %u min_limit = %u max_limit = %u num_sending = %u bytes_since_avg = %llu", UServer_Base::throttling_rec->krate, UServer_Base::throttling_rec->min_limit, UServer_Base::throttling_rec->max_limit, UServer_Base::throttling_rec->num_sending, UServer_Base::throttling_rec->bytes_since_avg) UServer_Base::throttling_rec->bytes_since_avg += UServer_Base::pClientImage->bytes_sent; // NB: db can have different pattern matching the same url... U_RETURN(1); } static int updateSendingRate(UStringRep* key, UStringRep* data) { U_TRACE(0, "UBandWidthThrottling::updateSendingRate(%p,%p)", key, data) if (key) { if (UServices::dosMatchWithOR(UServer_Base::pClientImage->uri, U_STRING_TO_PARAM(*key))) U_RETURN(4); // NB: call us later (after set record value from db)... U_RETURN(1); } U_INTERNAL_DUMP("krate = %u min_limit = %u max_limit = %u num_sending = %u bytes_since_avg = %llu", UServer_Base::throttling_rec->krate, UServer_Base::throttling_rec->min_limit, UServer_Base::throttling_rec->max_limit, UServer_Base::throttling_rec->num_sending, UServer_Base::throttling_rec->bytes_since_avg) U_INTERNAL_ASSERT_MAJOR(UServer_Base::throttling_rec->num_sending, 0) uint32_t l = UServer_Base::throttling_rec->max_limit / UServer_Base::throttling_rec->num_sending; UServer_Base::pClientImage->max_limit = (UServer_Base::pClientImage->max_limit == U_NOT_FOUND ? l : U_min(l, UServer_Base::pClientImage->max_limit)); U_RETURN(1); } static bool updateSendingRate(void* cimg) { U_TRACE(0, "UBandWidthThrottling::updateSendingRate(%p)", cimg) U_INTERNAL_ASSERT_POINTER(cimg) U_INTERNAL_DUMP("pthis = %p handler_other = %p handler_inotify = %p ", UServer_Base::pthis, UServer_Base::handler_other, UServer_Base::handler_inotify) if (cimg == UServer_Base::pthis || cimg == UServer_Base::handler_other || cimg == UServer_Base::handler_inotify) { U_RETURN(false); } U_INTERNAL_ASSERT(((UClientImage_Base*)cimg)->socket->isOpen()) UServer_Base::pClientImage = ((UClientImage_Base*)cimg); U_INTERNAL_DUMP("UServer_Base::pClientImage->min_limit = %u UServer_Base::pClientImage->max_limit = %u", UServer_Base::pClientImage->min_limit, UServer_Base::pClientImage->max_limit) UServer_Base::pClientImage->max_limit = U_NOT_FOUND; UServer_Base::db_throttling->callForAllEntry(updateSendingRate); U_RETURN(false); } // define method VIRTUAL of class UEventTime virtual int handlerTime() U_DECL_FINAL { U_TRACE_NO_PARAM(0, "UBandWidthThrottling::handlerTime()") if (UServer_Base::db_throttling) { // Update the average sending rate for each throttle. This is only used when new connections start up UServer_Base::db_throttling->callForAllEntry(updateThrottling); // Now update the sending rate on all the currently-sending connections, redistributing it evenly U_INTERNAL_DUMP("UNotifier::num_connection = %u UNotifier::min_connection = %u", UNotifier::num_connection, UNotifier::min_connection) if (UNotifier::num_connection > UNotifier::min_connection) UNotifier::callForAllEntryDynamic(updateSendingRate); } U_RETURN(0); // monitoring } #if defined(DEBUG) && defined(U_STDCPP_ENABLE) const char* dump(bool _reset) const { return UEventTime::dump(_reset); } #endif private: U_DISALLOW_COPY_AND_ASSIGN(UBandWidthThrottling) }; class U_NO_EXPORT UClientThrottling : public UEventTime { public: UClientThrottling(UClientImage_Base* _pClientImage, long sec, long micro_sec) : UEventTime(sec, micro_sec) { U_TRACE_CTOR(0, UClientThrottling, "%p,%ld,%ld", _pClientImage, sec, micro_sec) UNotifier::suspend(pClientImage = _pClientImage); } virtual ~UClientThrottling() U_DECL_FINAL { U_TRACE_DTOR(0, UClientThrottling) } // define method VIRTUAL of class UEventTime virtual int handlerTime() U_DECL_FINAL { U_TRACE_NO_PARAM(0, "UClientThrottling::handlerTime()") UNotifier::resume(pClientImage); U_RETURN(-1); // normal } #if defined(DEBUG) && defined(U_STDCPP_ENABLE) const char* dump(bool _reset) const { return UEventTime::dump(_reset); } #endif protected: UClientImage_Base* pClientImage; private: U_DISALLOW_COPY_AND_ASSIGN(UClientThrottling) }; void UServer_Base::initThrottlingClient() { U_TRACE_NO_PARAM(0, "UServer_Base::initThrottlingClient()") if (db_throttling) { pClientImage->uri.clear(); pClientImage->min_limit = pClientImage->max_limit = U_NOT_FOUND; pClientImage->bytes_sent = pClientImage->started_at = 0; } } void UServer_Base::initThrottlingServer() { U_TRACE_NO_PARAM(0, "UServer_Base::initThrottlingServer()") U_INTERNAL_ASSERT(*throttling_mask) U_INTERNAL_ASSERT_EQUALS(db_throttling, U_NULLPTR) if (bssl) { U_WARNING("Sorry, we can't use bandwidth throttling with SSL"); // NB: we need to use sendfile()... } else { U_NEW(URDBObjectHandler, db_throttling, URDBObjectHandler(U_STRING_FROM_CONSTANT("../db/BandWidthThrottling"), -1, &throttling_rec, true)); if (db_throttling->open(32 * 1024, false, true, true, U_SRV_LOCK_THROTTLING)) // NB: we don't want truncate (we have only the journal)... { char* ptr; UString pattern, number; UVector vec(*throttling_mask); U_SRV_LOG("db BandWidthThrottling initialization success: size(%u)", db_throttling->size()); db_throttling->reset(); // Initialize the db to contain no entries UServer_Base::uthrottling rec = { 0, 0, 0, 0, 0 }; for (int32_t i = 0, n = vec.size(); i < n; i += 2) { pattern = vec[i]; number = vec[i+1]; rec.max_limit = ::strtol(number.data(), &ptr, 10); if (ptr[0] == '-') rec.min_limit = rec.max_limit, rec.max_limit = ::strtol( ptr+1, U_NULLPTR, 10); (void) db_throttling->insertDataStorage(&rec, sizeof(uthrottling), U_STRING_TO_PARAM(pattern), RDB_INSERT); } min_size_for_sendfile = 4096; // 4k } else { U_SRV_LOG("WARNING: db BandWidthThrottling initialization failed"); U_DELETE(db_throttling) db_throttling = U_NULLPTR; } } } void UServer_Base::clearThrottling() { U_TRACE_NO_PARAM(0, "UServer_Base::clearThrottling()") U_INTERNAL_ASSERT(pClientImage) U_INTERNAL_ASSERT(pClientImage->uri) U_INTERNAL_ASSERT_POINTER(db_throttling) U_INTERNAL_DUMP("pClientImage->uri = %V", pClientImage->uri.rep) db_throttling->callForAllEntry(UBandWidthThrottling::clearThrottling); } bool UServer_Base::checkThrottling() { U_TRACE_NO_PARAM(0, "UServer_Base::checkThrottling()") if (db_throttling) { throttling_chk = true; pClientImage->max_limit = pClientImage->min_limit = U_NOT_FOUND; db_throttling->callForAllEntry(UBandWidthThrottling::checkThrottling); if (throttling_chk == false) U_RETURN(false); (void) pClientImage->uri.replace(U_HTTP_URI_TO_PARAM); } U_RETURN(true); } bool UServer_Base::checkThrottlingBeforeSend(bool bwrite) { U_TRACE(0, "UServer_Base::checkThrottlingBeforeSend(%b)", bwrite) if (db_throttling) { U_ASSERT(pClientImage->uri.equal(U_HTTP_URI_TO_PARAM)) U_INTERNAL_DUMP("pClientImage->max_limit = %u pClientImage->bytes_sent = %llu", pClientImage->max_limit, pClientImage->bytes_sent) if (pClientImage->max_limit != U_NOT_FOUND) { U_gettimeofday // NB: optimization if it is enough a time resolution of one second... if (bwrite == false) { U_INTERNAL_ASSERT_EQUALS(pClientImage->started_at, 0) U_ASSERT(pClientImage->uri.equal(U_HTTP_URI_TO_PARAM)) U_INTERNAL_DUMP("pClientImage->bytes_sent = %llu UClientImage_Base::ncount = %u", pClientImage->bytes_sent, UClientImage_Base::ncount) pClientImage->started_at = u_now->tv_sec; pClientImage->setPendingSendfile(); db_throttling->callForAllEntry(UBandWidthThrottling::updateSending); U_RETURN(false); } // check if we're sending too fast uint32_t elapsed = u_now->tv_sec - pClientImage->started_at, kbytes_sent = pClientImage->bytes_sent / 1024ULL, krate = (elapsed > 1 ? kbytes_sent / elapsed : kbytes_sent); U_INTERNAL_DUMP("krate = %u elapsed = %u", krate, elapsed) if (krate > pClientImage->max_limit) { // how long should we wait to get back on schedule? If less than a second (integer math rounding), use 1/2 second uint32_t div = pClientImage->max_limit - elapsed; if (div == 0) div = 1; time_t coast = kbytes_sent / div; // set up the wakeup timer UClientThrottling* pc; U_NEW(UClientThrottling, pc, UClientThrottling(pClientImage, coast, coast ? 0 : U_SECOND / 2L)); UTimer::insert(pc); U_RETURN(false); } } } U_RETURN(true); } #endif /** * This code provide evasive action in the event of an HTTP DoS or DDoS attack or brute force attack. It is also designed to be a * detection tool, and can be easily configured to talk to ipchains, firewalls, routers, and etcetera. Detection is performed by * creating an internal dynamic hash table of IP Addresses and URIs, and denying any single IP address from any of the following: * * - Requesting the same page more than a few times per second * - Making more than 50 concurrent requests per second * - Making any requests while temporarily blacklisted (on a blocking list) * * This method has worked well in both single-server script attacks as well as distributed attacks, but just like other evasive tools, * is only as useful to the point of bandwidth and processor consumption (e.g. the amount of bandwidth and processor required to * receive/process/respond to invalid requests), which is why it's a good idea to integrate this with your firewalls and routers. * * This code has a built-in cleanup mechanism and scaling capabilities. Because of this, legitimate requests are rarely ever compromised, * only legitimate attacks. Even a user repeatedly clicking on 'reload' should not be affected unless they do it maliciously, * * A web hit request comes in. The following steps take place: * * - The IP address of the requestor is looked up on the temporary blacklist. * - The IP address of the requestor is hashed into a "key". A lookup is performed in the listener's internal hash table to determine if * the same host has requested more than 50 objects within the past second. * - The IP address of the requestor and the URI are both hashed into a "key". A lookup is performed in the listener's internal hash table * to determine if the same host has requested this page more than 3 within the past 1 second. * * If any of the above are true, a RESET is sent. This conserves bandwidth and system resources in the event of a DoS attack. Additionally, * a system command and/or an email notification can also be triggered to block all the originating addresses of a DDoS attack. * * Once a single RESET incident occurs, evasive code now blocks the entire IP address for a period of 10 seconds (configurable). If the host * requests a page within this period, it is forced to wait even longer. Since this is triggered from requesting the same URL multiple times * per second, this again does not affect legitimate users. * * The blacklist can/should be configured to talk to your network's firewalls and/or routers to push the attack out to the front lines, but * this is not required. This tool is *excellent* at fending off request-based DoS attacks or scripted attacks, and brute force attacks. * When integrated with firewalls or IP filters, evasive code can stand up to even large attacks. Its features will prevent you from wasting * bandwidth or having a few thousand CGI scripts running as a result of an attack. * * If you do not have an infrastructure capable of fending off any other types of DoS attacks, chances are this tool will only help you to the * point of your total bandwidth or server capacity for sending RESET's. Without a solid infrastructure and address filtering tool in place, a * heavy distributed DoS will most likely still take you offline */ #ifdef U_EVASIVE_SUPPORT bool UServer_Base::bwhitelist; UFile* UServer_Base::dos_LOG; uint32_t UServer_Base::page_count; uint32_t UServer_Base::site_count; uint32_t UServer_Base::site_interval; uint32_t UServer_Base::page_interval; uint32_t UServer_Base::blocking_period; UString* UServer_Base::dosEmailAddress; UString* UServer_Base::systemCommand; UVector* UServer_Base::vwhitelist_IP; UServer_Base::uevasive* UServer_Base::evasive_rec; URDBObjectHandler* UServer_Base::db_evasive; void UServer_Base::initEvasive() { U_TRACE_NO_PARAM(0, "UServer_Base::initEvasive()") U_INTERNAL_ASSERT_EQUALS(db_evasive, U_NULLPTR) U_NEW(URDBObjectHandler, db_evasive, URDBObjectHandler(U_STRING_FROM_CONSTANT("../db/Evasive"), -1, &evasive_rec, true)); // POSIX shared memory object: interprocess - can be used by unrelated processes (userver_tcp and userver_ssl) if (db_evasive->open(128 * U_1M, false, true, true, U_SHM_LOCK_EVASIVE)) // NB: we don't want truncate (we have only the journal)... { U_SRV_LOG("db Evasive initialization success: size(%u)", db_evasive->size()); URDB::initRecordLock(); db_evasive->reset(); // Initialize the db to contain no entries if (dos_LOG) (void) UServer_Base::addLog(dos_LOG); } else { U_SRV_LOG("WARNING: db Evasive initialization failed"); U_DELETE(db_evasive) db_evasive = U_NULLPTR; } } bool UServer_Base::checkHold(in_addr_t client) { U_TRACE(0, "UServer_Base::checkHold(%u)", client) U_INTERNAL_ASSERT_POINTER(db_evasive) bool result = false; if ((bwhitelist = (vwhitelist_IP && UIPAllow::isAllowed(client, *vwhitelist_IP))) == false) // Check whitelist { U_gettimeofday // NB: optimization if it is enough a time resolution of one second... // First see if the IP itself is on "hold" if (db_evasive->getDataStorage(client)) { db_evasive->lockRecord(); if ((u_now->tv_sec - evasive_rec->timestamp) < blocking_period) { result = true; evasive_rec->timestamp = u_now->tv_sec; // Make it wait longer in blacklist land } db_evasive->unlockRecord(); } } U_RETURN(result); } bool UServer_Base::checkHitStats(const char* key, uint32_t key_len, uint32_t interval, uint32_t count) { U_TRACE(0, "UServer_Base::checkHitStats(%.*S,%u,%u,%u)", key_len, key, key_len, interval, count) U_INTERNAL_ASSERT_POINTER(db_evasive) U_INTERNAL_ASSERT_EQUALS(bwhitelist, false) bool binterval; if (db_evasive->getDataStorage(key, key_len) == false) { UServer_Base::uevasive rec = { (uint32_t)u_now->tv_sec, 0 }; (void) db_evasive->insertDataStorage(&rec, sizeof(uevasive), key, key_len, RDB_INSERT); U_RETURN(false); } db_evasive->lockRecord(); binterval = ((u_now->tv_sec - evasive_rec->timestamp) >= interval); // If site/URI is being hit too much, add to "hold" list if (binterval || evasive_rec->count < count) { if (binterval) evasive_rec->count = 0; // Reset our hit count list as necessary evasive_rec->count++; evasive_rec->timestamp = u_now->tv_sec; db_evasive->unlockRecord(); U_RETURN(false); } U_INTERNAL_ASSERT_EQUALS(binterval, false) U_INTERNAL_ASSERT(evasive_rec->count >= count) evasive_rec->count = 0; evasive_rec->timestamp = u_now->tv_sec; db_evasive->unlockRecord(); in_addr_t client = UServer_Base::getClientAddress(); if (db_evasive->getDataStorage(client)) evasive_rec->timestamp = u_now->tv_sec; // Make it wait longer in blacklist land else { UServer_Base::uevasive rec = { (uint32_t)u_now->tv_sec, 0 }; (void) db_evasive->insertDataStorage(&rec, sizeof(uevasive), client, RDB_INSERT); } /* U_INTERNAL_DUMP("u_now->tv_sec = %ld evasive_rec->timestamp = %u evasive_rec->count = %u", u_now->tv_sec, evasive_rec->timestamp, evasive_rec->count) U_INTERNAL_ASSERT_EQUALS(evasive_rec->timestamp, (uint32_t)u_now->tv_sec) */ char lmsg[4096]; uint32_t msg_len; bool bmail = false; if (count == site_count) { msg_len = u__snprintf(lmsg, sizeof(lmsg), U_CONSTANT_TO_PARAM("(pid %P) it has requested more than %u objects within the past %u seconds"), site_count, site_interval); if (dosEmailAddress) { if ((u_now->tv_sec - U_SHM_LAST_TIME_EMAIL_DOS) > U_ONE_DAY_IN_SECOND) { bmail = true; U_SHM_LAST_TIME_EMAIL_DOS = u_now->tv_sec; } } } else { uint32_t sz; const char* ptr = UClientImage_Base::getRequestUri(sz); msg_len = u__snprintf(lmsg, sizeof(lmsg), U_CONSTANT_TO_PARAM("(pid %P) it has requested the same page %.*S more than %u within the past %u seconds"), sz, ptr, page_count, page_interval); UClientImage_Base::abortive_close(); } U_DEBUG("blacklisting address %.*S, possible DoS attack: %.*s", U_CLIENT_ADDRESS_TO_TRACE, msg_len, lmsg); U_SRV_LOG("WARNING: blacklisting address %.*S, possible DoS attack: %.*s", U_CLIENT_ADDRESS_TO_TRACE, msg_len, lmsg); if (dos_LOG) ULog::log(dos_LOG->getFd(), U_CONSTANT_TO_PARAM("blacklisting %.*S: %.*s"), U_CLIENT_ADDRESS_TO_TRACE, msg_len, lmsg); if (bmail || systemCommand) { // Perform system functions and/or email notification pid_t pid = UServer_Base::startNewChild(); if (pid > 0) U_RETURN(true); // parent // child if (systemCommand) manageCommand(U_STRING_TO_PARAM(*systemCommand), U_CLIENT_ADDRESS_TO_TRACE, msg_len, lmsg); if (bmail) { U_INTERNAL_ASSERT_POINTER(emailClient) UString body(100U); body.snprintf(U_CONSTANT_TO_PARAM("blacklisting address %.*S, possible DoS attack: %.*s"), U_CLIENT_ADDRESS_TO_TRACE, msg_len, lmsg); emailClient->sendEmail(*dosEmailAddress, U_STRING_FROM_CONSTANT("possible DoS attack"), body); } if (pid == 0) UServer_Base::endNewChild(); } U_RETURN(true); } bool UServer_Base::checkHitSiteStats() { U_TRACE_NO_PARAM(0+256, "UServer_Base::checkHitSiteStats()") U_INTERNAL_ASSERT_POINTER(db_evasive) if (bwhitelist == false) { char key[6] = { '_', 0, 0, 0, 0, '_' }; u_put_unalignedp32(key+1, UServer_Base::getClientAddress()); U_gettimeofday // NB: optimization if it is enough a time resolution of one second... if (checkHitStats(key, sizeof(key), site_interval, site_count)) U_RETURN(true); } U_RETURN(false); } bool UServer_Base::checkHitUriStats() { U_TRACE_NO_PARAM(0+256, "UServer_Base::checkHitUriStats()") U_INTERNAL_ASSERT_POINTER(db_evasive) if (bwhitelist == false) { uint32_t sz; const char* ptr = UClientImage_Base::getRequestUri(sz); if (sz != U_CONSTANT_SIZE("/favicon.ico") && u_get_unalignedp64(ptr) != U_MULTICHAR_CONSTANT64('/','f','a','v','i','c','o','n') && u_get_unalignedp32(ptr+8) != U_MULTICHAR_CONSTANT32('.','i','c','o')) { char key[260]; uint32_t key_sz = (sz < 256 ? sz : 256), addr = UServer_Base::getClientAddress(); union uukey { char* k; uint32_t* u; }; union uukey ukey = { &key[0] }; u_put_unalignedp32(ukey.u, addr); U_MEMCPY(key+4, ptr, key_sz); if (checkHitStats(key, key_sz, page_interval, page_count)) U_RETURN(true); } } U_RETURN(false); } #endif #ifdef ENABLE_THREAD # include class UClientThread : public UThread { public: UClientThread() : UThread(PTHREAD_CREATE_DETACHED) {} virtual void run() U_DECL_FINAL { U_TRACE_NO_PARAM(0, "UClientThread::run()") U_INTERNAL_ASSERT_EQUALS(UServer_Base::ptime, U_NULLPTR) while (UServer_Base::flag_loop) UNotifier::waitForEvent(); } private: U_DISALLOW_COPY_AND_ASSIGN(UClientThread) }; # ifdef U_LINUX class UTimeThread : public UThread { public: UTimeThread() : UThread(PTHREAD_CREATE_DETACHED) {} virtual void run() U_DECL_FINAL { U_TRACE_NO_PARAM(0, "UTimeThread::run()") U_SRV_LOG("UTimeThread optimization for time resolution of one second activated (tid %u)", u_gettid()); # ifdef USE_LOAD_BALANCE int fd_sock = -1; uint32_t addr = 0; uusockaddr srv_addr, cli_addr; # if defined(USERVER_UDP) || defined(USERVER_IPC) if (UServer_Base::budp == false && UServer_Base::bipc == false) # endif { if (UServer_Base::ifname == U_NULLPTR) U_NEW_STRING(UServer_Base::ifname, UString(UServer_Base::getNetworkDevice(U_NULLPTR))); if (*UServer_Base::ifname) { fd_sock = UServer_Base::udp_sock->getFd(); if (fd_sock != -1) { UServer_Base::udp_sock->reOpen(); fd_sock = UServer_Base::udp_sock->getFd(); } UServer_Base::udp_sock->setReuseAddress(); srv_addr.psaIP4Addr.sin_port = htons(UServer_Base::port); srv_addr.psaIP4Addr.sin_family = PF_INET; srv_addr.psaIP4Addr.sin_addr.s_addr = htonl(INADDR_ANY); if (U_SYSCALL(bind, "%d,%p,%d", fd_sock, &(srv_addr.psaGeneric), sizeof(uusockaddr))) U_ERROR("bind on udp socket failed"); if (UIPAddress::setBroadcastAddress(srv_addr, *UServer_Base::ifname) == false) { U_ERROR("set broadcast address on interface %V failed", UServer_Base::ifname->rep); } if (UServer_Base::udp_sock->setSockOpt(SOL_SOCKET, SO_BROADCAST, (const int[]){ 1 }) == false) U_ERROR("setting SO_BROADCAST on udp socket failed"); UServer_Base::udp_sock->setNonBlocking(); addr = UServer_Base::socket->cLocalAddress.get_addr(); U_DUMP("addr = %V", UIPAddress::toString(addr).rep) U_SRV_LOG("Load balance activated (by udp socket: %u): loadavg_threshold = %u brodacast address = (%v:%v)", fd_sock, UServer_Base::loadavg_threshold, UServer_Base::ifname->rep, UIPAddress::toString(srv_addr.psaIP4Addr.sin_addr.s_addr).rep); } } # endif u_gettimenow(); struct timespec ts = { 0L, 0L }; uint32_t lnow, sec = u_now->tv_sec; while (UServer_Base::flag_loop) { ts.tv_nsec = 10000L + (U_SECOND - u_now->tv_usec) * 1000L; U_INTERNAL_DUMP("u_now->tv_sec = %ld u_now->tv_usec = %ld ts.tv_nsec = %ld", u_now->tv_sec, u_now->tv_usec, ts.tv_nsec) if (U_SYSCALL(nanosleep, "%p,%p", &ts, U_NULLPTR) == -1) { U_INTERNAL_ASSERT_EQUALS(errno, EINTR) U_INTERNAL_DUMP("UServer_Base::flag_loop = %b", UServer_Base::flag_loop) u_gettimenow(); continue; } # if !defined(U_SERVER_CAPTIVE_PORTAL) && !defined(U_LOG_DISABLE) && defined(USE_LIBZ) if (UServer_Base::log) UServer_Base::log->checkForLogRotateDataToWrite(); if (UServer_Base::apache_like_log) UServer_Base::apache_like_log->checkForLogRotateDataToWrite(); # endif # ifdef USE_LOAD_BALANCE if (fd_sock > 0) { U_SRV_MY_LOAD = u_get_loadavg(); U_SRV_MIN_LOAD_REMOTE = 255; U_INTERNAL_DUMP("U_SRV_MY_LOAD = %u", U_SRV_MY_LOAD) int iBytesTransferred = U_SYSCALL(sendto, "%d,%p,%u,%u,%p,%d", fd_sock, &U_SRV_MY_LOAD, sizeof(char), MSG_DONTROUTE, (sockaddr*)&(srv_addr.psaGeneric), sizeof(srv_addr)); if (iBytesTransferred == sizeof(char)) { unsigned char datagram[4096]; socklen_t slDummy = sizeof(cli_addr); uint8_t min_loadavg_remote = 255; uint32_t min_loadavg_remote_ip = 0; while ((iBytesTransferred = U_SYSCALL(recvfrom,"%d,%p,%u,%u,%p,%p", fd_sock, datagram, sizeof(datagram), MSG_DONTWAIT, (sockaddr*)&(cli_addr.psaGeneric),&slDummy)) > 0) { U_DUMP("Received datagram from (%V:%u) = (%u,%#.*S)", UIPAddress::toString(cli_addr.psaIP4Addr.sin_addr.s_addr).rep, ntohs(cli_addr.psaIP4Addr.sin_port), iBytesTransferred, iBytesTransferred, datagram) if (iBytesTransferred == 1) { if (( cli_addr.psaIP4Addr.sin_addr.s_addr == addr && ntohs(cli_addr.psaIP4Addr.sin_port) == UServer_Base::port) || (UServer_Base::vallow_cluster && UIPAllow::isAllowed(cli_addr.psaIP4Addr.sin_addr.s_addr, *UServer_Base::vallow_cluster) == false)) { continue; } if (datagram[0] < min_loadavg_remote) { min_loadavg_remote = datagram[0]; u_put_unalignedp32(&min_loadavg_remote_ip, srv_addr.psaIP4Addr.sin_addr.s_addr); } } } U_SRV_MIN_LOAD_REMOTE = min_loadavg_remote; u_put_unalignedp32(&U_SRV_MIN_LOAD_REMOTE_IP, min_loadavg_remote_ip); U_DUMP("U_SRV_MIN_LOAD_REMOTE = %u U_SRV_MIN_LOAD_REMOTE_IP = %s", U_SRV_MIN_LOAD_REMOTE, UIPAddress::getAddressString(U_SRV_MIN_LOAD_REMOTE_IP)) } } # endif u_gettimenow(); U_INTERNAL_DUMP("u_now->tv_sec = %ld u_now->tv_usec = %ld", u_now->tv_sec, u_now->tv_usec) sec = u_now->tv_sec; # ifndef U_SERVER_CAPTIVE_PORTAL if (daylight && (sec % U_ONE_HOUR_IN_SECOND) == 0) { (void) UTimeDate::checkForDaylightSavingTime(sec); } # endif if (UServer_Base::update_date) { # ifndef U_SERVER_CAPTIVE_PORTAL (void) U_SYSCALL(pthread_rwlock_wrlock, "%p", ULog::prwlock); # endif if ((sec % U_ONE_HOUR_IN_SECOND) != 0) { if (UServer_Base::update_date1) UTimeDate::updateTime(ULog::ptr_shared_date->date1 + 12); if (UServer_Base::update_date2) UTimeDate::updateTime(ULog::ptr_shared_date->date2 + 15); if (UServer_Base::update_date3) UTimeDate::updateTime(ULog::ptr_shared_date->date3 + 26); } else { lnow = u_get_localtime(sec); if (UServer_Base::update_date1) (void) u_strftime2(ULog::ptr_shared_date->date1, 17, U_CONSTANT_TO_PARAM("%d/%m/%y %T"), lnow); if (UServer_Base::update_date2) (void) u_strftime2(ULog::ptr_shared_date->date2, 26-6, U_CONSTANT_TO_PARAM("%d/%b/%Y:%T"), lnow); if (UServer_Base::update_date3) (void) u_strftime2(ULog::ptr_shared_date->date3+6, 29-4, U_CONSTANT_TO_PARAM("%a, %d %b %Y %T"), sec); } # ifndef U_SERVER_CAPTIVE_PORTAL (void) U_SYSCALL(pthread_rwlock_unlock, "%p", ULog::prwlock); # endif } } } private: U_DISALLOW_COPY_AND_ASSIGN(UTimeThread) }; # endif # if defined(USE_LIBSSL) && !defined(OPENSSL_NO_OCSP) && defined(SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB) && !defined(_MSWINDOWS_) # include # include class UOCSPStapling : public UThread { public: UOCSPStapling() : UThread(PTHREAD_CREATE_DETACHED) {} virtual void run() U_DECL_FINAL { U_TRACE_NO_PARAM(0, "UOCSPStapling::run()") U_SRV_LOG("SSL: OCSP Stapling thread activated (tid %u)", u_gettid()); struct timespec ts = { 0L, 50000L }, rem = { 0L, 0L }; while (UServer_Base::flag_loop) { if (U_SYSCALL(nanosleep, "%p,%p", &ts, &rem) == -1) { U_INTERNAL_ASSERT_EQUALS(errno, EINTR) U_INTERNAL_DUMP("UServer_Base::flag_loop = %b rem.tv_sec = %ld", UServer_Base::flag_loop, rem.tv_sec) ts.tv_sec = rem.tv_sec; continue; } ts.tv_sec = USSLSocket::doStapling(); } } static bool init() { U_TRACE_NO_PARAM(0, "UOCSPStapling::init()") if (USSLSocket::setDataForStapling() == false) U_RETURN(false); USSLSocket::staple.data = UServer_Base::getPointerToDataShare(USSLSocket::staple.data); UServer_Base::setLockOCSPStaple(); U_INTERNAL_ASSERT_EQUALS(UServer_Base::pthread_ocsp, U_NULLPTR) U_NEW(UOCSPStapling, UServer_Base::pthread_ocsp, UOCSPStapling); U_INTERNAL_DUMP("UServer_Base::pthread_ocsp = %p", UServer_Base::pthread_ocsp) // NB: we must run before fork... UServer_Base::pthread_ocsp->start(0); U_RETURN(true); } private: U_DISALLOW_COPY_AND_ASSIGN(UOCSPStapling) }; ULock* UServer_Base::lock_ocsp_staple; UThread* UServer_Base::pthread_ocsp; # endif # ifdef U_SSE_ENABLE // SERVER SENT EVENTS (SSE) int UServer_Base::sse_event_fd; int UServer_Base::sse_socketpair[2]; char UServer_Base::iovbuf[1] = { 0 }; char UServer_Base::sse_fifo_name[256]; ULock* UServer_Base::lock_sse; uint32_t UServer_Base::sse_fifo_pos; UThread* UServer_Base::pthread_sse; UString* UServer_Base::sse_id; UString* UServer_Base::sse_event; struct iovec UServer_Base::iov[1] = { { iovbuf, 1 } }; struct msghdr UServer_Base::msg = { 0, 0, iov, 1, &cmsg, sizeof(struct ucmsghdr), 0 }; UVector* UServer_Base::sse_vclient; struct UServer_Base::ucmsghdr UServer_Base::cmsg = { sizeof(struct ucmsghdr), SOL_SOCKET, SCM_RIGHTS, 0 }; # ifdef USE_LIBSSL ULock* UServer_Base::lock_sse_ssl; # endif class USSEThread; class U_NO_EXPORT USSEClient { public: // Check for memory error U_MEMORY_TEST // Allocator e Deallocator U_MEMORY_ALLOCATOR U_MEMORY_DEALLOCATOR USSEClient(const UString& id, const UString& subscribe, int _fd, bool process) : sub(subscribe), uniq_id(id), fd(_fd), bprocess(process) { U_TRACE_CTOR(0, USSEClient, "%V,%V,%d,%b", id.rep, subscribe.rep, _fd, process) U_INTERNAL_ASSERT_DIFFERS(fd, -1) } ~USSEClient() { U_TRACE_DTOR(0, USSEClient) UFile::close(fd); if (bprocess) { UServer_Base::setFIFOForSSE(uniq_id); (void) UFile::_unlink(UServer_Base::sse_fifo_name); } } #if defined(DEBUG) && defined(U_STDCPP_ENABLE) const char* dump(bool _reset) const { return ""; } #endif protected: UString sub, // Token of subscribe (optional). Can be set by parameter "subscribe" uniq_id; // unique client id (optional). Can be set by parameter "id" int fd; bool bprocess; bool sendMsg(const UString& message, UString* pevent) { U_TRACE(0, "USSEClient::sendMsg(%V,%p)", message.rep, pevent) if (bprocess) { if (U_SYSCALL(write, "%u,%S,%u", fd, U_STRING_TO_PARAM(message)) <= 0) U_RETURN(false); } else { U_INTERNAL_ASSERT_EQUALS(UServer_Base::bssl, false) UString tmp = UServer_Base::printSSE(U_SRV_SSE_CNT1, message, pevent); if (U_SYSCALL(write, "%u,%S,%u", fd, U_STRING_TO_PARAM(tmp)) <= 0) U_RETURN(false); } U_RETURN(true); } private: friend class USSEThread; }; class USSEThread : public UThread { public: USSEThread() : UThread(PTHREAD_CREATE_DETACHED) {} virtual void run() U_DECL_FINAL { U_TRACE_NO_PARAM(0, "USSEThread::run()") int fd; const char* ptr; USSEClient* client; bool ball, bprocess; UVector vec; UVector vmessage; UString row, _id, token, rID, message, tmp; uint32_t pos, mr, sz, last_event_id, i, n, k, start = 0, end = 0; char buffer_input[ 64U * 1024U], buffer_output[64U * 1024U]; UString input(buffer_input, sizeof(buffer_input)), output(buffer_output, sizeof(buffer_output)); while (U_SRV_FLAG_SIGTERM == false) { if (UNotifier::waitForRead(UServer_Base::sse_event_fd) == 1 && UServices::read(UServer_Base::sse_event_fd, input)) { /** * NEW * DEL * * LIST - List clients to /tmp/SSE_list.txt * * MSG - Send message to the stream * = - Send message to the subscribers of * -= - Send message to the subscribers of (* => all) except */ for (k = 0, sz = vec.split(input); k < sz; ++k) { row = vec[k]; pos = row.find('='); if (pos > 32) { ptr = row.data(); if (u_get_unalignedp32(ptr) == U_MULTICHAR_CONSTANT32('N','E','W',' ')) // NEW { _id = vec[k+1]; token = vec[k+2]; last_event_id = vec[k+3].strtoul(); bprocess = ((fd = vec[k+4].strtol()) == -1); U_INTERNAL_DUMP("_id = %V token = %V fd = %d bprocess = %b last_event_id = %u U_SRV_SSE_CNT1 = %u", _id.rep, token.rep, fd, bprocess, last_event_id, U_SRV_SSE_CNT1) if ((ball = token.equal(*UString::str_asterisk))) token = *UString::str_asterisk; else token.duplicate(); if (last_event_id && last_event_id < U_SRV_SSE_CNT1) { UVector vec1; vmessage.getFromLast(last_event_id, start, end, vec1); for (i = 0, n = vec1.size(); i < n; ++i) { message = vec1[i]; pos = message.find('='); U_INTERNAL_DUMP("vmessage[%u] = %V pos = %u", i % vmessage.capacity(), message.rep, pos) if (ball || token.equal(message.data(), pos)) { (void) output.append(UServer_Base::printSSE(i+1, message.substr(pos+1), (ball ? U_NULLPTR : &token))); } } output.push('\n'); (void) U_SYSCALL(write, "%u,%S,%u", UServer_Base::sse_event_fd, U_STRING_TO_PARAM(output)); output.setEmpty(); } if (bprocess == false) { // int rfd = fd; fd = (U_SYSCALL(recvmsg, "%u,%p,%u", UServer_Base::sse_socketpair[0], &UServer_Base::msg, 0) == 1 ? UServer_Base::cmsg.cmsg_data : -1); U_INTERNAL_DUMP("fd = %d", fd) // U_INTERNAL_ASSERT_EQUALS(fd, rfd) } else { UServer_Base::setFIFOForSSE(_id); (void) UFile::mkfifo(UServer_Base::sse_fifo_name); fd = UFile::open(UServer_Base::sse_fifo_name, O_WRONLY, 0); if (fd == -1) U_ERROR("Error on opening SSE FIFO: %S", UServer_Base::sse_fifo_name); } U_NEW(USSEClient, client, USSEClient(_id.copy(), token, fd, bprocess)); UServer_Base::sse_vclient->push(client); k += 4; } else if (u_get_unalignedp32(ptr) == U_MULTICHAR_CONSTANT32('D','E','L',' ')) // DEL { _id = vec[k+1]; U_INTERNAL_DUMP("_id = %V", _id.rep) for (i = 0, n = UServer_Base::sse_vclient->size(); i < n; ++i) { client = UServer_Base::sse_vclient->at(i); U_INTERNAL_DUMP("sse_vclient[%u]->uniq_id = %V", i, client->uniq_id.rep) if (client->uniq_id == _id) { --n; U_DELETE(UServer_Base::sse_vclient->remove(i--)) break; } } k += 1; } # ifdef DEBUG else if (u_get_unalignedp32(ptr) == U_MULTICHAR_CONSTANT32('L','I','S','T')) { UString row1(U_CAPACITY); for (i = 0, n = UServer_Base::sse_vclient->size(); i < n; ++i) { client = UServer_Base::sse_vclient->at(i); row1.snprintf(U_CONSTANT_TO_PARAM("uniq_id: %V sub: %V fd: %u proc: %b\n"), client->uniq_id.rep, client->sub.rep, client->fd, client->bprocess); (void) output.append(row1); } U_FILE_WRITE_TO_TMP(output, "SSE_client.txt"); output = vmessage.join(0, U_CONSTANT_TO_PARAM("\n")); U_FILE_WRITE_TO_TMP(output, "SSE_message.txt"); output.setEmpty(); ++k; } # endif else if (u_get_unalignedp32(ptr) == U_MULTICHAR_CONSTANT32('M','S','G',' ')) // MSG { _id = vec[k+1]; message = vec[k+2]; U_INTERNAL_DUMP("_id = %V message = %V U_SRV_SSE_CNT1 = %u", _id.rep, message.rep, U_SRV_SSE_CNT1) for (i = 0, n = UServer_Base::sse_vclient->size(); i < n; ++i) { client = UServer_Base::sse_vclient->at(i); U_INTERNAL_DUMP("sse_vclient[%u]->uniq_id = %V", i, client->uniq_id.rep) if (client->uniq_id == _id) { U_SRV_LOG("[sse] send message(%u) to %V: %V", U_SRV_SSE_CNT1, _id.rep, message.rep); if (client->sendMsg(message, U_NULLPTR) == false) U_DELETE(UServer_Base::sse_vclient->remove(i)) break; } } k += 2; } else { U_WARNING("Wrong formatted message from SSE FIFO, ignored: row = %V", row.rep); ++k; } } else { mr = row.find('-', 0, pos); if (mr == U_NOT_FOUND) token = row.substr(0U, pos); else { token = row.substr(0U, mr); rID = row.substr(mr+1, pos-mr-1); } message = row.substr(pos+1); U_SRV_SSE_CNT1++; U_INTERNAL_DUMP("token = %V rID = %V message = %V U_SRV_SSE_CNT1 = %u", token.rep, rID.rep, message.rep, U_SRV_SSE_CNT1) U_SRV_LOG("[sse] send message(%u) to subscribers of %V%.*s%.*s: %V", U_SRV_SSE_CNT1, token.rep, rID ? U_CONSTANT_SIZE(" except SSE_") : 0, " except SSE_", rID ? rID.size() : 0, rID.data(), message.rep); tmp = ((ball = token.equal(*UString::str_asterisk)) ? "*="+message : token+'='+message); vmessage.insertWithBound(tmp, start, end); for (i = 0, n = UServer_Base::sse_vclient->size(); i < n; ++i) { client = UServer_Base::sse_vclient->at(i); if (rID != client->uniq_id && (ball || client->sub.equal(token))) { if (client->sendMsg(message, (ball ? U_NULLPTR : &token)) == false) { --n; U_DELETE(UServer_Base::sse_vclient->remove(i--)) } } } } } // UServer_Base::unlockSSE(); vec.clear(); rID.clear(); tmp.clear(); token.clear(); message.clear(); input.setEmpty(); } } } private: U_DISALLOW_COPY_AND_ASSIGN(USSEThread) }; # endif #endif #ifdef U_LINUX static long sysctl_somaxconn, tcp_abort_on_overflow, sysctl_max_syn_backlog, tcp_fin_timeout; #endif UServer_Base::UServer_Base(UFileConfig* pcfg) { U_TRACE_CTOR(0, UServer_Base, "%p", pcfg) U_INTERNAL_ASSERT_EQUALS(pthis, U_NULLPTR) U_INTERNAL_ASSERT_EQUALS(cenvironment, U_NULLPTR) U_INTERNAL_ASSERT_EQUALS(senvironment, U_NULLPTR) port = 80; pthis = this; U_NEW_STRING(as_user, UString); U_NEW_STRING(dh_file, UString); U_NEW_STRING(cert_file, UString); U_NEW_STRING(key_file, UString); U_NEW_STRING(password, UString); U_NEW_STRING(ca_file, UString); U_NEW_STRING(ca_path, UString); U_NEW_STRING(auth_ip, UString); U_NEW_STRING(name_sock, UString); U_NEW_STRING(IP_address, UString); U_NEW_STRING(cenvironment, UString(U_CAPACITY)); U_NEW_STRING(senvironment, UString(U_CAPACITY)); U_NEW_STRING(document_root, UString); U_NEW(UVector, vlog, UVector); u_init_ulib_hostname(); u_init_ulib_username(); u_init_security(); if (u_start_time == 0 && u_setStartTime() == false) { U_WARNING("System date not updated: %#5D", u_now->tv_sec); } /** * TMPDIR is the canonical Unix environment variable which points to user scratch space. Most Unix utilities will honor the setting of this * variable and use its value to denote the scratch area for temporary files instead of the common default of /tmp. Other forms sometimes * accepted are TEMP, TEMPDIR, and TMP but these are used more commonly by non-POSIX Operating systems. If defined, otherwise it will be * /tmp. By default, /tmp on Fedora 18 will be on a tmpfs. Storage of large temporary files should be done in /var/tmp. This will reduce the * I/O generated on disks, increase SSD lifetime, save power, and improve performance of the /tmp filesystem */ const char* tmpdir = (const char*) getenv("TMPDIR"); u_tmpdir = (tmpdir ? tmpdir : "/var/tmp"); if (pcfg) { U_INTERNAL_ASSERT_EQUALS(cfg, U_NULLPTR) cfg = pcfg; cfg->load(); if (cfg->empty() == false) loadConfigParam(); } #ifdef ENABLE_IPV6 if (UClientImage_Base::bIPv6) iAddressType = AF_INET6; else #endif iAddressType = AF_INET; } UServer_Base::~UServer_Base() { U_TRACE_DTOR(0, UServer_Base) U_INTERNAL_ASSERT_POINTER(socket) #ifdef ENABLE_THREAD # if !defined(USE_LIBEVENT) && defined(U_SERVER_THREAD_APPROACH_SUPPORT) if (preforked_num_kids == -1) { U_INTERNAL_ASSERT_POINTER(UNotifier::pthread) U_DELETE(UNotifier::pthread) } # endif # ifdef U_LINUX if (u_pthread_time) { U_DELETE((UTimeThread*)u_pthread_time) (void) pthread_rwlock_destroy(ULog::prwlock); } # if defined(USE_LIBSSL) && !defined(OPENSSL_NO_OCSP) && defined(SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB) if (bssl) { if (pthread_ocsp) U_DELETE(pthread_ocsp) USSLSocket::cleanupStapling(); if (lock_ocsp_staple) U_DELETE(lock_ocsp_staple) } # endif # ifdef U_SSE_ENABLE // SERVER SENT EVENTS (SSE) if (lock_sse) U_DELETE(lock_sse) # ifdef USE_LIBSSL if (lock_sse_ssl) U_DELETE(lock_sse_ssl) # endif if (pthread_sse) { U_DELETE(sse_id) U_DELETE(sse_vclient) U_DELETE((USSEThread*)pthread_sse) } # endif # endif #endif UClientImage_Base::clear(); U_DELETE(socket) U_DELETE(udp_sock) if (vplugin) { U_DELETE(vplugin_name) U_DELETE(vplugin) } UOrmDriver::clear(); U_INTERNAL_ASSERT_POINTER(cenvironment) U_INTERNAL_ASSERT_POINTER(senvironment) U_DELETE(auth_ip) U_DELETE(cenvironment) U_DELETE(senvironment) if (host) U_DELETE(host) if (emailClient) U_DELETE(emailClient) if (crashEmailAddress) U_DELETE(crashEmailAddress) #ifdef USE_LOAD_BALANCE if (ifname) U_DELETE(ifname) if (vallow_cluster) U_DELETE(vallow_cluster) #endif #ifdef U_THROTTLING_SUPPORT if (db_throttling) { db_throttling->close(); U_DELETE(db_throttling) } if (throttling_mask) U_DELETE(throttling_mask) #endif #ifdef U_EVASIVE_SUPPORT if (db_evasive) { db_evasive->close(); U_DELETE(db_evasive) } if (vwhitelist_IP) U_DELETE(vwhitelist_IP) if (dosEmailAddress) U_DELETE(dosEmailAddress) #endif #ifdef U_WELCOME_SUPPORT if (msg_welcome) U_DELETE(msg_welcome) #endif #ifdef U_ACL_SUPPORT if (vallow_IP) U_DELETE(vallow_IP) #endif #ifdef U_RFC1918_SUPPORT if (vallow_IP_prv) U_DELETE(vallow_IP_prv) #endif #ifndef USE_LIBEVENT if (ptime) U_DELETE(ptime) #endif #ifdef DEBUG if (pstat) U_DELETE(pstat) #endif #ifdef U_LINUX if (as_user->empty() && isChild() == false) { u_need_root(false); (void) UFile::setSysParam("/proc/sys/net/ipv4/tcp_fin_timeout", tcp_fin_timeout, true); if (USocket::iBackLog > SOMAXCONN) { (void) UFile::setSysParam("/proc/sys/net/core/somaxconn", sysctl_somaxconn, true); (void) UFile::setSysParam("/proc/sys/net/ipv4/tcp_max_syn_backlog", sysctl_max_syn_backlog, true); } if (USocket::iBackLog == 1) (void) UFile::setSysParam("/proc/sys/net/ipv4/tcp_abort_on_overflow", tcp_abort_on_overflow, true); } #endif if (proc) U_DELETE(proc) if (server) U_DELETE(server) if (lock_user1) U_DELETE(lock_user1) if (lock_user2) U_DELETE(lock_user2) if (ptr_shared_data) UFile::munmap(ptr_shared_data, map_size); U_DELETE(as_user) U_DELETE(dh_file) U_DELETE(cert_file) U_DELETE(key_file) U_DELETE(password) U_DELETE(ca_file) U_DELETE(ca_path) U_DELETE(name_sock) U_DELETE(IP_address) U_DELETE(document_root) UDynamic::clear(); UEventFd::fd = -1; // NB: to avoid to delete itself... UNotifier::num_connection = 0; UNotifier::clear(); UTimer::clear(); #ifndef U_LOG_DISABLE if (log) { log->ULog::close(); U_DELETE(log) } #endif } void UServer_Base::closeLog() { U_TRACE_NO_PARAM(0, "UServer_Base::closeLog()") #ifndef U_LOG_DISABLE if (log && log->isOpen()) { log->closeLog(); } if (apache_like_log && apache_like_log->isOpen()) { apache_like_log->closeLog(); # ifdef DEBUG U_DELETE(apache_like_log) # endif apache_like_log = U_NULLPTR; } UClient_Base::closeLog(); #endif if (vlog) { for (uint32_t i = 0, n = vlog->size(); i < n; ++i) { file_LOG* item = (*vlog)[i]; if (item->LOG->isOpen()) item->LOG->close(); U_DELETE(item->LOG) } vlog->clear(); # ifdef DEBUG U_DELETE(vlog) # endif vlog = U_NULLPTR; } } #ifdef U_WELCOME_SUPPORT void UServer_Base::setMsgWelcome(const UString& lmsg) { U_TRACE(0, "UServer_Base::setMsgWelcome(%V)", lmsg.rep) U_INTERNAL_ASSERT(lmsg) U_NEW_STRING(msg_welcome, UString(U_CAPACITY)); UEscape::decode(lmsg, *msg_welcome); if (*msg_welcome) (void) msg_welcome->shrink(); else { U_DELETE(msg_welcome) msg_welcome = U_NULLPTR; } } #endif bool UServer_Base::setDocumentRoot(const UString& dir) { U_TRACE(0, "UServer_Base::setDocumentRoot(%V)", dir.rep) *document_root = dir; if (document_root->empty() || document_root->equal(U_CONSTANT_TO_PARAM("."))) { (void) document_root->replace(u_cwd, (document_root_size = u_cwd_len)); } else { U_INTERNAL_ASSERT(document_root->isNullTerminated()) document_root_ptr = document_root->data(); char c = *document_root_ptr; if (c == '~' || c == '$') { *document_root = UStringExt::expandPath(*document_root, U_NULLPTR); document_root_ptr = document_root->data(); if (document_root->empty()) { U_WARNING("Var DOCUMENT_ROOT %S expansion failed", document_root_ptr); U_RETURN(false); } } *document_root = UFile::getRealPath(document_root_ptr, false); document_root_size = document_root->size(); } document_root_ptr = document_root->data(); U_INTERNAL_DUMP("document_root(%u) = %.*S", document_root_size, document_root_size, document_root_ptr) if (UFile::chdir(document_root_ptr, false) == false) { U_WARNING("Chdir to working directory (DOCUMENT_ROOT) %S failed", document_root_ptr); U_RETURN(false); } U_INTERNAL_ASSERT_POINTER(document_root) U_INTERNAL_ASSERT_EQUALS(document_root_size, u_cwd_len) U_INTERNAL_ASSERT_EQUALS(strncmp(document_root_ptr, u_cwd, u_cwd_len), 0) U_RETURN(true); } void UServer_Base::loadConfigParam() { U_TRACE_NO_PARAM(0, "UServer_Base::loadConfigParam()") U_INTERNAL_ASSERT_POINTER(cfg) // -------------------------------------------------------------------------------------------------------------------------------------- // userver - configuration parameters // -------------------------------------------------------------------------------------------------------------------------------------- // ENABLE_IPV6 flag indicating the use of ipv6 // SERVER host name or ip address for the listening socket // PORT port number for the listening socket // SOCKET_NAME file name for the listening socket // IP_ADDRESS ip address of host for the interface connected to the Internet (autodetected if not specified) // // ALLOWED_IP list of comma separated client address for IP-based access control (IPADDR[/MASK]) // ALLOWED_IP_PRIVATE list of comma separated client private address for IP-based access control (IPADDR[/MASK]) for public server // ENABLE_RFC1918_FILTER reject request from private IP to public server address // MIN_SIZE_FOR_SENDFILE for major size it is better to use sendfile() to serve static content // // LISTEN_BACKLOG max number of ready to be delivered connections to accept() // SET_REALTIME_PRIORITY flag indicating that the preforked processes will be scheduled under the real-time policies SCHED_FIFO // // CLIENT_THRESHOLD min number of clients to active polling // CLIENT_FOR_PARALLELIZATION min number of clients to active parallelization // // LOAD_BALANCE_CLUSTER list of comma separated IP address (IPADDR[/MASK]) to define the load balance cluster // LOAD_BALANCE_DEVICE_NETWORK network interface name of cluster of physical server // LOAD_BALANCE_LOADAVG_THRESHOLD system load threshold to proxies the request on other userver on the network cluster ([0-9].[0-9]) // // PID_FILE write pid on file indicated // WELCOME_MSG message of welcome to send initially to client // RUN_AS_USER downgrade the security to that of user account // DOCUMENT_ROOT The directory out of which you will serve your documents // // LOG_FILE locations for file log // LOG_FILE_SZ memory size for file log // LOG_MSG_SIZE limit length of print network message to LOG_MSG_SIZE chars (default 128) // // PLUGIN list of plugins to load, a flexible way to add specific functionality to the server // PLUGIN_DIR directory where there are plugins to load // // ORM_DRIVER list of ORM drivers to load, a flexible way to add specific functionality to the ORM // ORM_DRIVER_DIR directory where there are ORM drivers to load // // REQ_TIMEOUT timeout for request from client // TCP_KEEP_ALIVE Specifies to active the TCP keepalive implementation in the linux kernel // TCP_LINGER_SET Specifies how the TCP initiated the close // MAX_KEEP_ALIVE Specifies the maximum number of requests that can be served through a Keep-Alive (Persistent) session. (Value <= 0 will disable Keep-Alive) // // DH_FILE DH param // CERT_FILE server certificate // KEY_FILE server private key // PASSWORD password for server private key // CA_FILE locations of trusted CA certificates used in the verification // CA_PATH locations of trusted CA certificates used in the verification // VERIFY_MODE mode of verification (SSL_VERIFY_NONE=0, SSL_VERIFY_PEER=1, SSL_VERIFY_FAIL_IF_NO_PEER_CERT=2, SSL_VERIFY_CLIENT_ONCE=4) // CIPHER_SUITE cipher suite model (Intermediate=0, Modern=1, Old=2) // // PREFORK_CHILD number of child server processes created at startup: -1 - thread approach (experimental) // 0 - serialize, no forking // 1 - classic, forking after client accept // >1 - pool of serialized processes plus monitoring process // // CRASH_COUNT this is the threshold for the number of crash of child server processes // CRASH_EMAIL_NOTIFY the email address to send a message whenever the number of crash > CRASH_COUNT // -------------------------------------------------------------------------------------------------------------------------------------- // This directive are for evasive action in the event of an HTTP DoS or DDoS attack or brute force attack // -------------------------------------------------------------------------------------------------------------------------------------- // DOS_PAGE_COUNT this is the threshold for the number of requests for the same page (or URI) per page interval // DOS_PAGE_INTERVAL the interval for the page count threshold; defaults to 1 second intervals // DOS_SITE_COUNT this is the threshold for the total number of requests for any object by the same client per site interval // DOS_SITE_INTERVAL the interval for the site count threshold; defaults to 1 second intervals // DOS_BLOCKING_PERIOD the blocking period is the amount of time (in seconds) that a client will be blocked for if they are added to the blocking list (defaults to 10) // DOS_WHITE_LIST list of comma separated IP addresses of trusted clients can be whitelisted to insure they are never denied (IPADDR[/MASK]) // DOS_EMAIL_NOTIFY the email address to send a message whenever an IP address becomes blacklisted // DOS_SYSTEM_COMMAND the system command specified will be executed whenever an IP address becomes blacklisted. Use %.*s to denote the IP address of the blacklisted IP // DOS_LOGFILE the file to write DOS event // -------------------------------------------------------------------------------------------------------------------------------------- U_INTERNAL_DUMP("bssl = %b budp = %b bipc = %b", bssl, budp, bipc) #ifdef USERVER_IPC if (bipc) { # ifdef _MSWINDOWS_ U_ERROR("Sorry, I was compiled on Windows so there isn't UNIX domain sockets"); # endif *name_sock = cfg->at(U_CONSTANT_TO_PARAM("SOCKET_NAME")); if (name_sock->empty()) U_ERROR("Sorry, I cannot run without SOCKET_NAME value"); } #endif UString x = cfg->at(U_CONSTANT_TO_PARAM("SERVER")); if (x) U_NEW_STRING(server, UString(x)); *IP_address = cfg->at(U_CONSTANT_TO_PARAM("IP_ADDRESS")); #ifdef ENABLE_IPV6 UClientImage_Base::bIPv6 = cfg->readBoolean(U_CONSTANT_TO_PARAM("ENABLE_IPV6")); #endif timeoutMS = cfg->readLong(U_CONSTANT_TO_PARAM("REQ_TIMEOUT"), -1); if (timeoutMS > 0) timeoutMS *= 1000; port = cfg->readLong(U_CONSTANT_TO_PARAM("PORT"), bssl ? 443 : 80); if ((port == 80 || port == 443) && UServices::isSetuidRoot() == false) { unsigned int _port = (port == 80 ? 8080 : 4433); U_WARNING("Sorry, it is required root privilege to listen on port %u but I am not setuid root, I must try %u", port, _port); port = _port; } U_INTERNAL_DUMP("SOMAXCONN = %d FD_SETSIZE = %d", SOMAXCONN, FD_SETSIZE) set_tcp_keep_alive = cfg->readBoolean(U_CONSTANT_TO_PARAM("TCP_KEEP_ALIVE")); set_realtime_priority = cfg->readBoolean(U_CONSTANT_TO_PARAM("SET_REALTIME_PRIORITY"), true); crash_count = cfg->readLong(U_CONSTANT_TO_PARAM("CRASH_COUNT"), 5); tcp_linger_set = cfg->readLong(U_CONSTANT_TO_PARAM("TCP_LINGER_SET"), -2); USocket::iBackLog = cfg->readLong(U_CONSTANT_TO_PARAM("LISTEN_BACKLOG"), SOMAXCONN); min_size_for_sendfile = cfg->readLong(U_CONSTANT_TO_PARAM("MIN_SIZE_FOR_SENDFILE"), 500 * 1024); // 500k: for major size we assume is better to use sendfile() num_client_threshold = cfg->readLong(U_CONSTANT_TO_PARAM("CLIENT_THRESHOLD")); UNotifier::max_connection = cfg->readLong(U_CONSTANT_TO_PARAM("MAX_KEEP_ALIVE")); u_printf_string_max_length = cfg->readLong(U_CONSTANT_TO_PARAM("LOG_MSG_SIZE")); #ifdef USERVER_UDP if (budp && u_printf_string_max_length == -1) { u_printf_string_max_length = 128; } #endif x = cfg->at(U_CONSTANT_TO_PARAM("CRASH_EMAIL_NOTIFY")); if (x) { U_INTERNAL_ASSERT_EQUALS(crashEmailAddress, U_NULLPTR) U_NEW_STRING(crashEmailAddress, UString(x)); if (emailClient == U_NULLPTR) { U_NEW(USmtpClient, emailClient, USmtpClient(UClientImage_Base::bIPv6)); } } #ifdef U_WELCOME_SUPPORT x = cfg->at(U_CONSTANT_TO_PARAM("WELCOME_MSG")); if (x) setMsgWelcome(x); #endif x = cfg->at(U_CONSTANT_TO_PARAM("PREFORK_CHILD")); if (x) { preforked_num_kids = x.strtol(); # if !defined(ENABLE_THREAD) || defined(USE_LIBEVENT) || !defined(U_SERVER_THREAD_APPROACH_SUPPORT) if (preforked_num_kids == -1) { U_WARNING("Sorry, I was compiled without server thread approach so I can't accept PREFORK_CHILD == -1"); preforked_num_kids = 2; } # endif } #ifndef _MSWINDOWS_ else { preforked_num_kids = u_get_num_cpu(); U_INTERNAL_DUMP("num_cpu = %d", preforked_num_kids) if (preforked_num_kids < 2) preforked_num_kids = 2; } #endif #ifndef U_CLASSIC_SUPPORT if (isClassic()) { U_WARNING("Sorry, I was compiled without server classic model support so I can't accept PREFORK_CHILD == 1"); preforked_num_kids = 2; } #endif #ifdef _MSWINDOWS_ if (preforked_num_kids > 0) { U_WARNING("Sorry, I was compiled on Windows so I can't accept PREFORK_CHILD > 0"); preforked_num_kids = 0; } #endif #ifdef USE_LIBSSL if (bssl) { *dh_file = cfg->at(U_CONSTANT_TO_PARAM("DH_FILE")); *ca_file = cfg->at(U_CONSTANT_TO_PARAM("CA_FILE")); *ca_path = cfg->at(U_CONSTANT_TO_PARAM("CA_PATH")); *key_file = cfg->at(U_CONSTANT_TO_PARAM("KEY_FILE")); *password = cfg->at(U_CONSTANT_TO_PARAM("PASSWORD")); *cert_file = cfg->at(U_CONSTANT_TO_PARAM("CERT_FILE")); verify_mode = cfg->readLong(U_CONSTANT_TO_PARAM("VERIFY_MODE")); min_size_for_sendfile = U_NOT_FOUND; // NB: we can't use sendfile with SSL... } #endif U_INTERNAL_DUMP("min_size_for_sendfile = %u", min_size_for_sendfile) // Instructs server to accept connections from the IP address IPADDR. A CIDR mask length can be // supplied optionally after a trailing slash, e.g. 192.168.0.0/24, in which case addresses that // match in the most significant MASK bits will be allowed. If no options are specified, all clients // are allowed. Unauthorized connections are rejected by closing the TCP connection immediately. A // warning is logged on the server but nothing is sent to the client #ifdef U_ACL_SUPPORT x = cfg->at(U_CONSTANT_TO_PARAM("ALLOWED_IP")); if (x) { U_INTERNAL_ASSERT_EQUALS(vallow_IP, U_NULLPTR) U_NEW(UVector, vallow_IP, UVector); if (UIPAllow::parseMask(x, *vallow_IP) == 0) { U_DELETE(vallow_IP) vallow_IP = U_NULLPTR; } } #endif #ifdef U_RFC1918_SUPPORT x = cfg->at(U_CONSTANT_TO_PARAM("ALLOWED_IP_PRIVATE")); if (x) { U_INTERNAL_ASSERT_EQUALS(vallow_IP_prv, U_NULLPTR) U_NEW(UVector, vallow_IP_prv, UVector); if (UIPAllow::parseMask(x, *vallow_IP_prv) == 0) { U_DELETE(vallow_IP_prv) vallow_IP_prv = U_NULLPTR; } } enable_rfc1918_filter = cfg->readBoolean(U_CONSTANT_TO_PARAM("ENABLE_RFC1918_FILTER")); #endif // If you want the webserver to run as a process of a defined user, you can do it. // For the change of user to work, it's necessary to execute the server with root privileges. // If it's started by a user that that doesn't have root privileges, this step will be omitted #ifndef U_LOG_DISABLE bool bmsg = false; #endif x = cfg->at(U_CONSTANT_TO_PARAM("RUN_AS_USER")); if (x) { if (UServices::isSetuidRoot()) { U_INTERNAL_ASSERT(x.isNullTerminated()) struct passwd* pw = (struct passwd*) U_SYSCALL(getpwnam, "%S", x.data()); if (pw && pw->pw_dir) { *as_user = x; # ifdef DEBUG UMemoryPool::obj_class = ""; UMemoryPool::func_call = __PRETTY_FUNCTION__; # endif char* buffer = (char*)UMemoryPool::pop(U_SIZE_TO_STACK_INDEX(U_PATH_MAX)); (void) snprintf(buffer, U_PATH_MAX, "HOME=%s", pw->pw_dir); (void) U_SYSCALL(putenv, "%S", buffer); # ifdef DEBUG UMemoryPool::obj_class = UMemoryPool::func_call = U_NULLPTR; # endif } } # ifndef U_LOG_DISABLE else bmsg = true; # endif } // DOCUMENT_ROOT: The directory out of which we will serve your documents #ifdef USERVER_UDP if (budp == false) #endif { if (setDocumentRoot(cfg->at(U_CONSTANT_TO_PARAM("DOCUMENT_ROOT"))) == false) U_ERROR("Setting DOCUMENT ROOT to %V failed", document_root->rep); } #ifndef U_LOG_DISABLE x = cfg->at(U_CONSTANT_TO_PARAM("LOG_FILE")); if (x) { // open log update_date = update_date1 = true; U_NEW(ULog, log, ULog(x, cfg->readLong(U_CONSTANT_TO_PARAM("LOG_FILE_SZ")))); log->init(U_CONSTANT_TO_PARAM(U_SERVER_LOG_PREFIX)); # ifdef USERVER_UDP if (budp == false) # endif { U_SRV_LOG("Working directory (DOCUMENT_ROOT) changed to %.*S", u_cwd_len, u_cwd); } if (bmsg) U_SRV_LOG("WARNING: the \"RUN_AS_USER\" directive makes sense only if the master process runs with super-user privileges, ignored"); } #endif x = cfg->at(U_CONSTANT_TO_PARAM("PID_FILE")); if (x) { // write pid on file U_INTERNAL_ASSERT(x.isNullTerminated()) int old_pid = (int) UFile::getSysParam(x.data()); if (old_pid > 0) { U_SRV_LOG("Trying to kill another instance of userver that is running with pid %d", old_pid); U_INTERNAL_ASSERT_DIFFERS(old_pid, u_pid) UProcess::kill(old_pid, SIGTERM); // SIGTERM is sent to every process in the process group of the calling process... } (void) UFile::writeTo(x, u_pid_str, u_pid_str_len); U_DEBUG("We have %s the PID_FILE %V with content: %P", (old_pid > 0 ? "updated" : "created"), x.rep); } #ifdef USE_LOAD_BALANCE # ifdef USERVER_UDP if (budp == false) # endif { x = cfg->at(U_CONSTANT_TO_PARAM("LOAD_BALANCE_DEVICE_NETWORK")); if (x) { U_INTERNAL_ASSERT_EQUALS(ifname, U_NULLPTR) U_NEW_STRING(ifname, UString(x)); } x = cfg->at(U_CONSTANT_TO_PARAM("LOAD_BALANCE_LOADAVG_THRESHOLD")); if (x) loadavg_threshold = u_loadavg(x.data()); // 0.19 => 2, 4.56 => 46, ... U_INTERNAL_DUMP("loadavg_threshold = %u", loadavg_threshold) x = cfg->at(U_CONSTANT_TO_PARAM("LOAD_BALANCE_CLUSTER")); if (x) { U_INTERNAL_ASSERT_EQUALS(vallow_cluster, U_NULLPTR) U_NEW(UVector, vallow_cluster, UVector); if (UIPAllow::parseMask(x, *vallow_cluster) == 0) { U_DELETE(vallow_cluster) vallow_cluster = U_NULLPTR; } } } #endif #ifdef U_EVASIVE_SUPPORT # ifdef USERVER_UDP if (budp == false) # endif { /** * This is the threshold for the number of requests for the same page (or URI) per page interval. * Once the threshold for that interval has been exceeded (defaults to 2), the IP address of the client will be added to the blocking list */ page_count = cfg->readLong(U_CONSTANT_TO_PARAM("DOS_PAGE_COUNT"), 2); /** * The interval for the page count threshold; defaults to 1 second intervals */ page_interval = cfg->readLong(U_CONSTANT_TO_PARAM("DOS_PAGE_INTERVAL"), 1); /** * This is the threshold for the total number of requests for any object by the same client per site interval. * Once the threshold for that interval has been exceeded (defaults to 50), the IP address of the client will be added to the blocking list */ site_count = cfg->readLong(U_CONSTANT_TO_PARAM("DOS_SITE_COUNT"), 50); /** * The interval for the site count threshold; defaults to 1 second intervals */ site_interval = cfg->readLong(U_CONSTANT_TO_PARAM("DOS_SITE_INTERVAL"), 1); /** * The blocking period is the amount of time (in seconds) that a client will be blocked for if they are added to the blocking list (defaults to 10). * During this time, all subsequent requests from the client will result in a abortive close and the timer being reset (e.g. another 10 seconds). * Since the timer is reset for every subsequent request, it is not necessary to have a long blocking period; in the event of a DoS attack, this * timer will keep getting reset */ blocking_period = cfg->readLong(U_CONSTANT_TO_PARAM("DOS_BLOCKING_PERIOD"), 10); /** * IP addresses of trusted clients can be whitelisted to insure they are never denied. The purpose of whitelisting is to protect software, scripts, local * searchbots, or other automated tools from being denied for requesting large amounts of data from the server. Whitelisting should *not* be used to add * customer lists or anything of the sort, as this will open the server to abuse. This module is very difficult to trigger without performing some type of * malicious attack, and for that reason it is more appropriate to allow the module to decide on its own whether or not an individual customer should be blocked */ x = cfg->at(U_CONSTANT_TO_PARAM("DOS_WHITE_LIST")); if (x) { U_INTERNAL_ASSERT_EQUALS(vwhitelist_IP, U_NULLPTR) U_NEW(UVector, vwhitelist_IP, UVector); if (UIPAllow::parseMask(x, *vwhitelist_IP) == 0) { U_DELETE(vwhitelist_IP) vwhitelist_IP = U_NULLPTR; } } /** * If this value is set, an email will be sent to the address specified whenever an IP address becomes blacklisted */ x = cfg->at(U_CONSTANT_TO_PARAM("DOS_EMAIL_NOTIFY")); if (x) { U_INTERNAL_ASSERT_EQUALS(dosEmailAddress, U_NULLPTR) U_NEW_STRING(dosEmailAddress, UString(x)); if (emailClient == U_NULLPTR) { U_NEW(USmtpClient, emailClient, USmtpClient(UClientImage_Base::bIPv6)); } } /** * If this value is set, the system command specified will be executed whenever an IP address becomes blacklisted. * This is designed to enable system calls to ip filter or other tools. Use %.*s to denote the IP address of the blacklisted IP */ x = cfg->at(U_CONSTANT_TO_PARAM("DOS_SYSTEM_COMMAND")); if (x) { U_INTERNAL_ASSERT_EQUALS(systemCommand, U_NULLPTR) U_NEW_STRING(systemCommand, UString(x)); } x = cfg->at(U_CONSTANT_TO_PARAM("DOS_LOGFILE")); if (x) { U_INTERNAL_ASSERT_EQUALS(dos_LOG, U_NULLPTR) U_NEW(UFile, dos_LOG, UFile(x)); } } #endif // load ORM driver modules... #if defined(USE_SQLITE) || defined(USE_MYSQL) || defined(USE_PGSQL) UString orm_driver_dir = cfg->at(U_CONSTANT_TO_PARAM("ORM_DRIVER_DIR")), orm_driver_list = cfg->at(U_CONSTANT_TO_PARAM("ORM_DRIVER")); if (UOrmDriver::loadDriver(orm_driver_dir, orm_driver_list) == false) U_ERROR("ORM drivers load failed"); #endif // load plugin modules and call server-wide hooks handlerConfig()... #ifdef USERVER_UDP if (budp == false) #endif { UString plugin_dir = cfg->at(U_CONSTANT_TO_PARAM("PLUGIN_DIR")), plugin_list = cfg->at(U_CONSTANT_TO_PARAM("PLUGIN")); if (loadPlugins(plugin_dir, plugin_list) != U_PLUGIN_HANDLER_FINISHED) U_ERROR("Plugins stage load failed"); } } U_NO_EXPORT void UServer_Base::loadStaticLinkedModules(const UString& name) { U_TRACE(0, "UServer_Base::loadStaticLinkedModules(%V)", name.rep) U_INTERNAL_ASSERT_POINTER(vplugin_name) U_INTERNAL_ASSERT_MAJOR(vplugin_size, 0) if (vplugin_name->find(name) != U_NOT_FOUND) // NB: we load only the plugin that we want from configuration (PLUGIN var)... { #if defined(U_STATIC_HANDLER_RPC) || defined(U_STATIC_HANDLER_SHIB) || defined(U_STATIC_HANDLER_ECHO) || \ defined(U_STATIC_HANDLER_STREAM) || defined(U_STATIC_HANDLER_SOCKET) || defined(U_STATIC_HANDLER_SCGI) || \ defined(U_STATIC_HANDLER_FCGI) || defined(U_STATIC_HANDLER_GEOIP) || defined(U_STATIC_HANDLER_PROXY) || \ defined(U_STATIC_HANDLER_SOAP) || defined(U_STATIC_HANDLER_SSI) || defined(U_STATIC_HANDLER_TSA) || \ defined(U_STATIC_HANDLER_NOCAT) || defined(U_STATIC_HANDLER_HTTP) const UServerPlugIn* _plugin = U_NULLPTR; # ifdef U_STATIC_HANDLER_RPC if (name.equal(U_CONSTANT_TO_PARAM("rpc"))) { U_NEW_WITHOUT_CHECK_MEMORY(URpcPlugIn, _plugin, URpcPlugIn); goto next; } # endif # ifdef U_STATIC_HANDLER_SHIB if (name.equal(U_CONSTANT_TO_PARAM("shib"))) { U_NEW_WITHOUT_CHECK_MEMORY(UShibPlugIn, _plugin, UShibPlugIn); goto next; } # endif # ifdef U_STATIC_HANDLER_ECHO if (name.equal(U_CONSTANT_TO_PARAM("echo"))) { U_NEW_WITHOUT_CHECK_MEMORY(UEchoPlugIn, _plugin, UEchoPlugIn); goto next; } # endif # ifdef U_STATIC_HANDLER_STREAM if (name.equal(U_CONSTANT_TO_PARAM("stream"))) { U_NEW_WITHOUT_CHECK_MEMORY(UStreamPlugIn, _plugin, UStreamPlugIn); goto next; } # endif # ifdef U_STATIC_HANDLER_SOCKET if (name.equal(U_CONSTANT_TO_PARAM("socket"))) { U_NEW_WITHOUT_CHECK_MEMORY(UWebSocketPlugIn, _plugin, UWebSocketPlugIn); goto next; } # endif # ifdef U_STATIC_HANDLER_SCGI if (name.equal(U_CONSTANT_TO_PARAM("scgi"))) { U_NEW_WITHOUT_CHECK_MEMORY(USCGIPlugIn, _plugin, USCGIPlugIn); goto next; } # endif # ifdef U_STATIC_HANDLER_FCGI if (name.equal(U_CONSTANT_TO_PARAM("fcgi"))) { U_NEW_WITHOUT_CHECK_MEMORY(UFCGIPlugIn, _plugin, UFCGIPlugIn); goto next; } # endif # ifdef U_STATIC_HANDLER_GEOIP if (name.equal(U_CONSTANT_TO_PARAM("geoip"))) { U_NEW_WITHOUT_CHECK_MEMORY(UGeoIPPlugIn, _plugin, UGeoIPPlugIn); goto next; } # endif # ifdef U_STATIC_HANDLER_PROXY if (name.equal(U_CONSTANT_TO_PARAM("proxy"))) { U_NEW_WITHOUT_CHECK_MEMORY(UProxyPlugIn, _plugin, UProxyPlugIn); goto next; } # endif # ifdef U_STATIC_HANDLER_SOAP if (name.equal(U_CONSTANT_TO_PARAM("soap"))) { U_NEW_WITHOUT_CHECK_MEMORY(USoapPlugIn, _plugin, USoapPlugIn); goto next; } # endif # ifdef U_STATIC_HANDLER_SSI if (name.equal(U_CONSTANT_TO_PARAM("ssi"))) { U_NEW_WITHOUT_CHECK_MEMORY(USSIPlugIn, _plugin, USSIPlugIn); goto next; } # endif # ifdef U_STATIC_HANDLER_TSA if (name.equal(U_CONSTANT_TO_PARAM("tsa"))) { U_NEW_WITHOUT_CHECK_MEMORY(UTsaPlugIn, _plugin, UTsaPlugIn); goto next; } # endif # ifdef U_STATIC_HANDLER_NOCAT if (name.equal(U_CONSTANT_TO_PARAM("nocat"))) { U_NEW_WITHOUT_CHECK_MEMORY(UNoCatPlugIn, _plugin, UNoCatPlugIn); goto next; } # endif # ifdef U_STATIC_HANDLER_NODOG if (name.equal(U_CONSTANT_TO_PARAM("nodog"))) { U_NEW_WITHOUT_CHECK_MEMORY(UNoDogPlugIn, _plugin, UNoDogPlugIn); goto next; } # endif # ifdef U_STATIC_HANDLER_HTTP if (name.equal(U_CONSTANT_TO_PARAM("http"))) { U_NEW_WITHOUT_CHECK_MEMORY(UHttpPlugIn, _plugin, UHttpPlugIn); goto next; } # endif next: if (_plugin) { vplugin_static->push_back(_plugin); vplugin_name_static->push_back(name); U_SRV_LOG("Link phase of static plugin %V success", name.rep); } else { U_SRV_LOG("WARNING: Link phase of static plugin %V failed", name.rep); } #endif } } int UServer_Base::loadPlugins(UString& plugin_dir, const UString& plugin_list) { U_TRACE(0, "UServer_Base::loadPlugins(%V,%V)", plugin_dir.rep, plugin_list.rep) if (plugin_dir) { if (IS_DIR_SEPARATOR(plugin_dir.first_char()) == false) // NB: we can't use relativ path because after we call chdir()... { U_INTERNAL_ASSERT(plugin_dir.isNullTerminated()) plugin_dir = UFile::getRealPath(plugin_dir.data()); } UDynamic::setPluginDirectory(plugin_dir); } U_NEW(UVector, vplugin_name, UVector(10U)); U_NEW(UVector, vplugin_name_static, UVector(20U)); U_NEW(UVector, vplugin, UVector(10U)); U_NEW(UVector, vplugin_static, UVector(10U)); int result; uint32_t i, pos; UString item, _name; UServerPlugIn* _plugin; if (plugin_list) vplugin_size = vplugin_name->split(U_STRING_TO_PARAM(plugin_list)); // NB: we don't use split with substr() cause of dependency from config var PLUGIN... else { vplugin_size = 1; vplugin_name->push_back(*UString::str_http); } /** * I do know that to include code in the middle of a function is hacky and dirty, * but this is the best solution that I could figure out. If you have some idea to * clean it up, please, don't hesitate and let me know */ # include "plugin/loader.autoconf.cpp" for (i = 0; i < vplugin_size; ++i) { item = vplugin_name->at(i); pos = vplugin_name_static->find(item); U_INTERNAL_DUMP("i = %u pos = %u item = %V", i, pos, item.rep) if (pos != U_NOT_FOUND) { vplugin_name_static->erase(pos); _plugin = vplugin_static->remove(pos); vplugin->push(_plugin); } else { _name.setBuffer(32U); _name.snprintf(U_CONSTANT_TO_PARAM("server_plugin_%v"), item.rep); _plugin = UPlugIn::create(U_STRING_TO_PARAM(_name)); if (_plugin) { vplugin->push(_plugin); U_SRV_LOG("Load phase of plugin %V success", item.rep); } else { U_SRV_LOG("WARNING: Load phase of plugin %V failed", item.rep); } } } U_INTERNAL_ASSERT_MAJOR(vplugin_size, 0) U_INTERNAL_ASSERT_EQUALS(vplugin->size(), vplugin_size) U_DELETE(vplugin_static) U_DELETE(vplugin_name_static) if (cfg) { // NB: we load configuration in reverse order respect to the content of config var PLUGIN... i = vplugin_size; do { item = vplugin_name->at(--i); if (cfg->searchForObjectStream(U_STRING_TO_PARAM(item))) { cfg->table.clear(); result = vplugin->at(i)->handlerConfig(*cfg); cfg->reset(); if (result) { if (result == U_PLUGIN_HANDLER_ERROR) { U_SRV_LOG("WARNING: Configuration phase of plugin %V failed", item.rep); U_RETURN(U_PLUGIN_HANDLER_ERROR); } U_SRV_LOG("Configuration phase of plugin %V success", item.rep); } } } while (i > 0); } U_RETURN(U_PLUGIN_HANDLER_FINISHED); } #ifdef U_LOG_DISABLE void UServer_Base::pluginsHandlerRequest() { U_TRACE_NO_PARAM(0, "UServer_Base::pluginsHandlerRequest()") U_INTERNAL_ASSERT_POINTER(vplugin) U_INTERNAL_ASSERT_MAJOR(vplugin_size, 0) int result; for (uint32_t i = 0; i < vplugin_size; ++i) { if ((result = vplugin->at(i)->handlerRequest())) { if (result == U_PLUGIN_HANDLER_ERROR) { U_ClientImage_state = U_PLUGIN_HANDLER_ERROR; return; } U_INTERNAL_ASSERT_EQUALS(result, U_PLUGIN_HANDLER_PROCESSED) if (UClientImage_Base::isRequestAlreadyProcessed() || U_ClientImage_parallelization == U_PARALLELIZATION_PARENT) { return; } } } } #else void UServer_Base::pluginsHandlerRequest() { U_TRACE_NO_PARAM(0, "UServer_Base::pluginsHandlerRequest()") U_INTERNAL_ASSERT_POINTER(vplugin) U_INTERNAL_ASSERT_MAJOR(vplugin_size, 0) int result; UString name; UServerPlugIn* _plugin; for (uint32_t i = 0; i < vplugin_size; ++i) { _plugin = vplugin->at(i); if (isLog() == false) { if ((result = _plugin->handlerRequest())) { if (result == U_PLUGIN_HANDLER_ERROR) { U_ClientImage_state = U_PLUGIN_HANDLER_ERROR; return; } U_INTERNAL_ASSERT_EQUALS(result, U_PLUGIN_HANDLER_PROCESSED) if (UClientImage_Base::isRequestAlreadyProcessed() || U_ClientImage_parallelization == U_PARALLELIZATION_PARENT) { return; } } continue; } name = vplugin_name->at(i); (void) u__snprintf(mod_name[0], sizeof(mod_name[0]), U_CONSTANT_TO_PARAM("[%v] "), name.rep); result = _plugin->handlerRequest(); mod_name[0][0] = '\0'; if (result) { if (result == U_PLUGIN_HANDLER_ERROR) { U_ClientImage_state = U_PLUGIN_HANDLER_ERROR; log->log(U_CONSTANT_TO_PARAM("WARNING: Request phase of plugin %V failed"), name.rep); return; } U_INTERNAL_ASSERT_EQUALS(result, U_PLUGIN_HANDLER_PROCESSED) if (U_ClientImage_parallelization == U_PARALLELIZATION_PARENT) return; log->log(U_CONSTANT_TO_PARAM("Request phase of plugin %V success"), name.rep); if (UClientImage_Base::isRequestAlreadyProcessed()) return; } } } #endif // NB: we call the various handlerXXX() in reverse order respect to the content of config var PLUGIN... #ifdef U_LOG_DISABLE # define U_PLUGIN_HANDLER_REVERSE(xxx) \ int UServer_Base::pluginsHandler##xxx() \ { \ U_TRACE_NO_PARAM(0, "UServer_Base::pluginsHandler"#xxx"()") \ \ U_INTERNAL_ASSERT_POINTER(vplugin) \ U_INTERNAL_ASSERT_MAJOR(vplugin_size, 0) \ \ uint32_t i = vplugin_size; \ \ do { \ if (vplugin->at(--i)->handler##xxx() == U_PLUGIN_HANDLER_ERROR) \ { \ U_RETURN(U_PLUGIN_HANDLER_ERROR); \ } \ } \ while (i > 0); \ \ U_RETURN(U_PLUGIN_HANDLER_FINISHED); \ } #else # define U_PLUGIN_HANDLER_REVERSE(xxx) \ int UServer_Base::pluginsHandler##xxx() \ { \ U_TRACE_NO_PARAM(0, "UServer_Base::pluginsHandler"#xxx"()") \ \ U_INTERNAL_ASSERT_POINTER(vplugin) \ U_INTERNAL_ASSERT_MAJOR(vplugin_size, 0) \ \ int result; \ UString name; \ UServerPlugIn* _plugin; \ uint32_t i = vplugin_size; \ \ do { \ _plugin = vplugin->at(--i); \ \ if (isLog() == false) \ { \ if ((result = _plugin->handler##xxx())) \ { \ if (result == U_PLUGIN_HANDLER_ERROR) U_RETURN(U_PLUGIN_HANDLER_ERROR);\ \ U_INTERNAL_ASSERT_EQUALS(result, U_PLUGIN_HANDLER_PROCESSED) \ } \ \ continue; \ } \ \ name = vplugin_name->at(i); \ \ (void) u__snprintf(mod_name[0], sizeof(mod_name[0]), \ U_CONSTANT_TO_PARAM("[%v] "), name.rep); \ \ result = _plugin->handler##xxx(); \ \ mod_name[0][0] = '\0'; \ \ if (result) \ { \ if (result == U_PLUGIN_HANDLER_ERROR) \ { \ log->log(U_CONSTANT_TO_PARAM("WARNING: "#xxx" phase of plugin " \ "%V failed"), name.rep); \ \ U_RETURN(U_PLUGIN_HANDLER_ERROR); \ } \ \ U_INTERNAL_ASSERT_EQUALS(result, U_PLUGIN_HANDLER_PROCESSED) \ \ if (U_ClientImage_parallelization != U_PARALLELIZATION_PARENT) \ { \ log->log(U_CONSTANT_TO_PARAM(#xxx" phase of plugin %V success"), \ name.rep); \ } \ } \ } \ while (i > 0); \ \ U_RETURN(U_PLUGIN_HANDLER_FINISHED); \ } #endif // Server-wide hooks U_PLUGIN_HANDLER_REVERSE(Init) // NB: we call handlerInit() in reverse order respect to the content of config var PLUGIN... U_PLUGIN_HANDLER_REVERSE(Run) // NB: we call handlerRun() in reverse order respect to the content of config var PLUGIN... U_PLUGIN_HANDLER_REVERSE(Fork) // NB: we call handlerFork() in reverse order respect to the content of config var PLUGIN... U_PLUGIN_HANDLER_REVERSE(Stop) // NB: we call handlerStop() in reverse order respect to the content of config var PLUGIN... // Connection-wide hooks U_PLUGIN_HANDLER_REVERSE(READ) // NB: we call handlerREAD() in reverse order respect to the content of config var PLUGIN... // SigHUP hook U_PLUGIN_HANDLER_REVERSE(SigHUP) // NB: we call handlerSigHUP() in reverse order respect to the content of config var PLUGIN... #undef U_PLUGIN_HANDLER #undef U_PLUGIN_HANDLER_REVERSE void UServer_Base::suspendThread() { U_TRACE_NO_PARAM(0, "UServer_Base::suspendThread()") #if defined(U_LINUX) && defined(ENABLE_THREAD) if (u_pthread_time) ((UTimeThread*)u_pthread_time)->suspend(); # if defined(USE_LIBSSL) && !defined(OPENSSL_NO_OCSP) && defined(SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB) if (pthread_ocsp) pthread_ocsp->suspend(); # endif # ifdef U_SSE_ENABLE // SERVER SENT EVENTS (SSE) if (pthread_sse) pthread_sse->suspend(); # endif #endif } void UServer_Base::init() { U_TRACE_NO_PARAM(1, "UServer_Base::init()") U_INTERNAL_ASSERT_POINTER(socket) U_INTERNAL_DUMP("bssl = %b budp = %b bipc = %b", bssl, budp, bipc) #ifdef USERVER_UDP if (budp) { U_ASSERT(socket->isUDP()) if (socket->setServer(port, U_NULLPTR) == false) { U_ERROR("Run as server UDP with port '%u' failed", port); } goto next; } #endif #ifdef USE_LIBSSL if (bssl) { U_ASSERT(((USSLSocket*)socket)->isSSL()) U_INTERNAL_ASSERT( dh_file->isNullTerminated()) U_INTERNAL_ASSERT( ca_file->isNullTerminated()) U_INTERNAL_ASSERT( ca_path->isNullTerminated()) U_INTERNAL_ASSERT( key_file->isNullTerminated()) U_INTERNAL_ASSERT( password->isNullTerminated()) U_INTERNAL_ASSERT(cert_file->isNullTerminated()) // Load our certificate if (((USSLSocket*)socket)->setContext( dh_file->data(), cert_file->data(), key_file->data(), password->data(), ca_file->data(), ca_path->data(), verify_mode) == false) { U_ERROR("SSL: server setContext() failed"); } } #endif #ifdef USERVER_IPC if (bipc) { U_ASSERT(socket->isIPC()) UUnixSocket::setPath(name_sock->data()); if (UUnixSocket::path == U_NULLPTR) U_ERROR("UNIX domain socket is not bound to a file system pathname"); } #endif if (socket->setServer(port, server) == false) { UString x = (server ? *server : U_STRING_FROM_CONSTANT("*")); U_ERROR("Run as server with local address '%v:%u' failed", x.rep, port); } #ifdef USERVER_UDP next: #endif U_SRV_LOG("SO_REUSEPORT status is: %susing", (USocket::breuseport ? "" : "NOT ")); // get name host U_NEW_STRING(host, UString(server ? *server : USocketExt::getNodeName())); if (port != 80) { host->push_back(':'); UStringExt::appendNumber32(*host, port); } U_SRV_LOG("HOST registered as: %v", host->rep); // get IP address host (default source) if (server && IP_address->empty() && u_isIPAddr(UClientImage_Base::bIPv6, U_STRING_TO_PARAM(*server))) { *IP_address = *server; } #ifdef _MSWINDOWS_ if (IP_address->empty() || socket->setHostName(*IP_address) == false) { U_ERROR("On windows we need a valid IP_ADDRESS value on configuration file"); } #endif /** * This code does NOT make a connection or send any packets (to 8.8.8.8 which is google DNS). * Since UDP is a stateless protocol connect() merely makes a system call which figures out how to * route the packets based on the address and what interface (and therefore IP address) it should * bind to. Returns an array containing the family (AF_INET), local port, and local address (which * is what we want) of the socket */ U_NEW(UUDPSocket, udp_sock, UUDPSocket(UClientImage_Base::bIPv6)); #ifdef USERVER_IPC if (bipc == false) #endif { if (udp_sock->connectServer(U_STRING_FROM_CONSTANT("8.8.8.8"), 1001)) { socket->setLocal(udp_sock->cLocalAddress); const char* p = socket->getLocalInfo(); UString ip(p, u__strlen(p, __PRETTY_FUNCTION__)); if ( IP_address->empty()) *IP_address = ip; else if (*IP_address != ip) { U_SRV_LOG("WARNING: SERVER IP ADDRESS from configuration (%V) differ from system interface (%V)", IP_address->rep, ip.rep); } } if (IP_address->empty()) { (void) IP_address->assign(U_CONSTANT_TO_PARAM("127.0.0.1")); socket->cLocalAddress.setLocalHost(UClientImage_Base::bIPv6); U_WARNING("getting IP_ADDRESS from system interface fail, we try using localhost"); } else { in_addr_t addr; if (UIPAddress::getBinaryForm(IP_address->c_str(), addr) == false) U_ERROR("IP_ADDRESS conversion fail: %V", IP_address->rep); socket->setAddress(&addr); public_address = (socket->cLocalAddress.isPrivate() == false); } } U_SRV_LOG("SERVER IP ADDRESS registered as: %v (%s)", IP_address->rep, (public_address ? "public" : "private")); #ifdef U_LINUX u_need_root(false); /** * timeout_timewait parameter: Determines the time that must elapse before TCP/IP can release a closed connection * and reuse its resources. This interval between closure and release is known as the TIME_WAIT state or twice the * maximum segment lifetime (2MSL) state. During this time, reopening the connection to the client and server cost * less than establishing a new connection. By reducing the value of this entry, TCP/IP can release closed connections * faster, providing more resources for new connections. Adjust this parameter if the running application requires rapid * release, the creation of new connections, and a low throughput due to many connections sitting in the TIME_WAIT state */ tcp_fin_timeout = UFile::getSysParam("/proc/sys/net/ipv4/tcp_fin_timeout"); if (tcp_fin_timeout > 30) tcp_fin_timeout = UFile::setSysParam("/proc/sys/net/ipv4/tcp_fin_timeout", 30, true); /** * sysctl_somaxconn (SOMAXCONN: 128) specifies the maximum number of sockets in state SYN_RECV per listen socket queue. * At listen(2) time the backlog is adjusted to this limit if bigger then that. * * sysctl_max_syn_backlog on the other hand is dynamically adjusted, depending on the memory characteristic of the system. * Default is 256, 128 for small systems and up to 1024 for bigger systems. * * The system limits (somaxconn & tcp_max_syn_backlog) specify a _maximum_, the user cannot exceed this limit with listen(2). * The backlog argument for listen on the other hand specify a _minimum_ */ if (USocket::iBackLog == 1) { // sysctl_tcp_abort_on_overflow when its on, new connections are reset once the backlog is exhausted tcp_abort_on_overflow = UFile::setSysParam("/proc/sys/net/ipv4/tcp_abort_on_overflow", 1, true); } else if (USocket::iBackLog > SOMAXCONN) { // NB: take a look at `netstat -s | grep overflowed` sysctl_somaxconn = UFile::getSysParam("/proc/sys/net/core/somaxconn"); if (sysctl_somaxconn < USocket::iBackLog) { int value = UFile::setSysParam("/proc/sys/net/core/somaxconn", USocket::iBackLog); if (value == USocket::iBackLog) sysctl_max_syn_backlog = UFile::setSysParam("/proc/sys/net/ipv4/tcp_max_syn_backlog", value * 8); else { U_WARNING("The TCP backlog (LISTEN_BACKLOG) setting of %u cannot be enforced because of OS error - " "/proc/sys/net/core/somaxconn is set to the lower value of %d", USocket::iBackLog, sysctl_somaxconn); } } } U_INTERNAL_DUMP("sysctl_somaxconn = %d tcp_abort_on_overflow = %b sysctl_max_syn_backlog = %d", sysctl_somaxconn, tcp_abort_on_overflow, sysctl_max_syn_backlog) #endif UTimer::init(UTimer::NOSIGNAL); UClientImage_Base::init(); USocket::accept4_flags = SOCK_CLOEXEC | SOCK_NONBLOCK; U_INTERNAL_ASSERT_EQUALS(proc, U_NULLPTR) U_NEW(UProcess, proc, UProcess); U_INTERNAL_ASSERT_POINTER(proc) proc->setProcessGroup(); #if !defined(U_LOG_DISABLE) && defined(USE_LIBZ) if (isLog() && log->isMemoryMapped()) { U_INTERNAL_ASSERT_EQUALS(shared_data_add, 0) shared_data_add = log->getSizeLogRotateData(); } # endif // init plugin modules, must run after the setting for shared log #ifdef USERVER_UDP if (budp == false) #endif { if (pluginsHandlerInit() != U_PLUGIN_HANDLER_FINISHED) U_ERROR("Plugins stage init failed"); } // manage shared data... U_INTERNAL_DUMP("shared_data_add = %u shm_data_add = %u", shared_data_add, shm_data_add) U_INTERNAL_ASSERT_EQUALS(ptr_shm_data, U_NULLPTR) U_INTERNAL_ASSERT_EQUALS(ptr_shared_data, U_NULLPTR) U_DEBUG("sizeof(shared_data) = %u sizeof(shm_data) = %u shared_data_add = %u shm_data_add = %u", sizeof(shared_data), sizeof(shm_data), shared_data_add, shm_data_add); // For portable use, a shared memory object should be identified by a name of the form /somename; that is, a null-terminated string of // up to NAME_MAX (i.e., 255) characters consisting of an initial slash, followed by one or more characters, none of which are slashes #ifndef U_SERVER_CAPTIVE_PORTAL shm_size = sizeof(shm_data) + shm_data_add; ptr_shm_data = (shm_data*) UFile::shm_open("/userver", shm_size); U_INTERNAL_ASSERT_POINTER(ptr_shm_data) #endif #ifndef U_LOG_DISABLE if (isLog() == false) #endif ULog::initDate(); flag_loop = true; // NB: UTimeThread loop depend on this setting... #if defined(U_LINUX) && defined(ENABLE_THREAD) map_size = sizeof(shared_data) + shared_data_add; ptr_shared_data = (shared_data*) UFile::mmap(&map_size); U_INTERNAL_ASSERT_POINTER(ptr_shared_data) U_INTERNAL_ASSERT_EQUALS(U_SRV_TOT_CONNECTION, 0) U_INTERNAL_ASSERT_DIFFERS(ptr_shared_data, MAP_FAILED) U_INTERNAL_ASSERT_EQUALS(ULog::ptr_shared_date, U_NULLPTR) bool _daylight = *u_pdaylight; int now_adjust = *u_pnow_adjust; /* GMT based time */ struct timeval tv = *u_now; *(u_now = &(ptr_shared_data->now_shared)) = tv; *(u_pdaylight = &(ptr_shared_data->daylight_shared)) = _daylight; *(u_pnow_adjust = &(ptr_shared_data->now_adjust_shared)) = now_adjust; ULog::ptr_shared_date = &(ptr_shared_data->log_date_shared); U_MEMCPY(ULog::ptr_shared_date, &ULog::date, sizeof(ULog::log_date)); // NB: we block SIGHUP and SIGTERM; the threads created will inherit a copy of the signal mask... # ifdef sigemptyset sigemptyset(&mask); # else (void) U_SYSCALL(sigemptyset, "%p", &mask); # endif # ifdef sigaddset sigaddset(&mask, SIGHUP); sigaddset(&mask, SIGTERM); # else (void) U_SYSCALL(sigaddset, "%p,%d", &mask, SIGHUP); (void) U_SYSCALL(sigaddset, "%p,%d", &mask, SIGTERM); # endif (void) U_SYSCALL(pthread_sigmask, "%d,%p,%p", SIG_BLOCK, &mask, U_NULLPTR); U_INTERNAL_ASSERT_EQUALS(ULog::prwlock, U_NULLPTR) U_INTERNAL_ASSERT_EQUALS(u_pthread_time, U_NULLPTR) # ifdef USERVER_UDP if (budp == false || UServer_Base::update_date) # endif { U_NEW_WITHOUT_CHECK_MEMORY(UTimeThread, u_pthread_time, UTimeThread); (void) UThread::initRwLock((ULog::prwlock = &(ptr_shared_data->rwlock))); ((UTimeThread*)u_pthread_time)->start(50); } #endif #if !defined(U_LOG_DISABLE) && defined(USE_LIBZ) if (isLog() && log->isMemoryMapped()) { log->setLogRotate(); log->setShared(&(ptr_shared_data->log_data_shared)); // NB: if log is memory mapped must be shared because the possibility of fork() by parallelization U_SRV_LOG("Mapped %u bytes (%u KB) of shared memory for %d preforked process", sizeof(shared_data) + shared_data_add, map_size / 1024, preforked_num_kids); } if (apache_like_log && apache_like_log->isMemoryMapped()) { apache_like_log->setLogRotate(); apache_like_log->setShared(&(ptr_shm_data->log_data_shared)); // NB: if apache like log is memory mapped must be shared because the possibility of condivision with userver_ssl U_SRV_LOG("Mapped %u bytes (%u KB) of shared memory for apache like log", apache_like_log->getSizeLogRotateData(), apache_like_log->getSizeLogRotateData() / 1024); } #endif #ifdef DEBUG U_NEW(UTimeStat, pstat, UTimeStat); UTimer::insert(pstat); #endif (void) UFile::_mkdir("../db"); #ifdef U_THROTTLING_SUPPORT if (db_throttling) { // set up the throttles timer UEventTime* throttling_time; U_NEW(UBandWidthThrottling, throttling_time, UBandWidthThrottling); UTimer::insert(throttling_time); } #endif socket_flags |= O_RDWR | O_CLOEXEC; #ifdef USERVER_UDP if (budp) { UNotifier::max_connection = 1; UNotifier::num_connection = UNotifier::min_connection = 0; } else #endif { // --------------------------------------------------------------------------------------------------------- // init notifier event manager // --------------------------------------------------------------------------------------------------------- #if defined(ENABLE_THREAD) && !defined(USE_LIBEVENT) && defined(U_SERVER_THREAD_APPROACH_SUPPORT) if (preforked_num_kids != -1) #endif { // --------------------------------------------------------------------------------------------------------- // NB: in the classic model we don't need to be notified for request of connection (loop: accept-fork) // and the forked child don't accept new client, but we need anyway the event manager because // the forked child must feel the possibly timeout for request from the new client... // --------------------------------------------------------------------------------------------------------- if (timeoutMS > 0 || isClassic() == false) { binsert = true; // NB: we ask to be notified for request of connection (=> accept) UNotifier::min_connection = 1; # ifndef USE_LIBEVENT if (timeoutMS > 0) U_NEW(UTimeoutConnection, ptime, UTimeoutConnection); # endif pthis->UEventFd::op_mask |= EPOLLET; pthis->UEventFd::op_mask &= ~EPOLLRDHUP; U_INTERNAL_ASSERT_EQUALS(pthis->UEventFd::op_mask, EPOLLIN | EPOLLET) /** * There may not always be a connection waiting after a SIGIO is delivered or select(2) or poll(2) return a readability * event because the connection might have been removed by an asynchronous network error or another thread before * accept() is called. If this happens then the call will block waiting for the next connection to arrive. To ensure * that accept() never blocks, the passed socket sockfd needs to have the O_NONBLOCK flag set (see socket(7)) */ socket_flags |= O_NONBLOCK; } if (handler_other) { UNotifier::min_connection++; handler_other->UEventFd::op_mask &= ~EPOLLRDHUP; } if (handler_inotify) { UNotifier::min_connection++; handler_inotify->UEventFd::op_mask &= ~EPOLLRDHUP; } } UNotifier::max_connection = (UNotifier::max_connection ? UNotifier::max_connection : USocket::iBackLog) + (UNotifier::num_connection = UNotifier::min_connection); if (num_client_threshold == 0) num_client_threshold = U_NOT_FOUND; } U_INTERNAL_DUMP("UNotifier::max_connection = %u UNotifier::min_connection = %u num_client_threshold = %u", UNotifier::max_connection, UNotifier::min_connection, num_client_threshold) pthis->preallocate(); #if defined(USE_LIBSSL) && defined(ENABLE_THREAD) && !defined(OPENSSL_NO_OCSP) && defined(SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB) && !defined(_MSWINDOWS_) if (bssl && UOCSPStapling::init() == false) { U_WARNING("SSL: OCSP stapling ignored, some error occured..."); } #endif #ifdef USERVER_UDP if (budp == false) #endif { /* #ifdef DEBUG if (u_trace_fd != -1 && UFile::getSysParam("/tmp/userver.thread.disable") != -1) { suspendThread(); } #endif */ if (pluginsHandlerRun() != U_PLUGIN_HANDLER_FINISHED) U_ERROR("Plugins stage run failed"); } if (u_start_time == 0 && u_setStartTime() == false) { U_ERROR("System date not updated"); } #ifdef U_THROTTLING_SUPPORT # ifdef USERVER_UDP if (budp == false) # endif if (throttling_mask) initThrottlingServer(); #endif #ifdef U_EVASIVE_SUPPORT # ifdef USERVER_UDP if (budp == false) # endif initEvasive(); #endif #ifdef U_SSE_ENABLE // SERVER SENT EVENTS (SSE) # ifdef USERVER_UDP if (budp == false) # endif { sse_fifo_pos = u__snprintf(sse_fifo_name, 256, U_CONSTANT_TO_PARAM("%s/SSE_%s_EVENT"), u_tmpdir, bssl ? "SSL" : "TCP") - U_CONSTANT_SIZE("EVENT"); (void) UFile::mkfifo(sse_fifo_name, PERM_FILE); sse_event_fd = UFile::open(sse_fifo_name, O_RDWR, PERM_FILE); if (sse_event_fd == -1) U_ERROR("Error on opening SSE FIFO: %S", sse_fifo_name); # ifndef DEBUG (void) UFile::_unlink(sse_fifo_name); # endif setLockSSE(); U_INTERNAL_ASSERT_EQUALS(sse_id, U_NULLPTR) U_INTERNAL_ASSERT_EQUALS(sse_vclient, U_NULLPTR) U_INTERNAL_ASSERT_EQUALS(pthread_sse, U_NULLPTR) U_NEW_STRING(sse_id, UString); U_NEW(USSEThread, pthread_sse, USSEThread); U_NEW(UVector, sse_vclient, UVector); pthread_sse->start(0); (void) U_SYSCALL(socketpair, "%d,%d,%d,%p", AF_UNIX, SOCK_STREAM, 0, sse_socketpair); U_INTERNAL_DUMP("sse_socketpair[0] = %u sse_socketpair[1] = %u", sse_socketpair[0], sse_socketpair[1]) U_SRV_LOG("SSE thread activated: %s(%d)", sse_fifo_name, sse_event_fd); } #endif if (cfg) cfg->clear(); UInterrupt::syscall_restart = false; UInterrupt::exit_loop_wait_event_for_signal = true; #if !defined(USE_LIBEVENT) && !defined(USE_RUBY) UInterrupt::insert( SIGHUP, (sighandler_t)UServer_Base::handlerForSigHUP); // async signal UInterrupt::insert( SIGTERM, (sighandler_t)UServer_Base::handlerForSigTERM); // async signal #else UInterrupt::setHandlerForSignal( SIGHUP, (sighandler_t)UServer_Base::handlerForSigHUP); // sync signal UInterrupt::setHandlerForSignal( SIGTERM, (sighandler_t)UServer_Base::handlerForSigTERM); // sync signal #endif } bool UServer_Base::addLog(UFile* plog, int flags) { U_TRACE(0, "UServer_Base::addLog(%p,%d)", plog, flags) U_INTERNAL_ASSERT_POINTER(vlog) if (plog->creat(flags, PERM_FILE)) { file_LOG* item; U_NEW_WITHOUT_CHECK_MEMORY(file_LOG, item, file_LOG); item->LOG = plog; item->flags = flags; vlog->push_back(item); U_RETURN(true); } U_RETURN(false); } void UServer_Base::sendSignalToAllChildren(int signo, sighandler_t handler) { U_TRACE(0, "UServer_Base::sendSignalToAllChildren(%d,%p)", signo, handler) U_INTERNAL_ASSERT_POINTER(proc) U_INTERNAL_ASSERT_POINTER(pthis) U_INTERNAL_ASSERT(proc->parent()) suspendThread(); // NB: we can't use UInterrupt::erase() because it restore the old action (UInterrupt::init)... UInterrupt::setHandlerForSignal(signo, (sighandler_t)SIG_IGN); pthis->handlerSignal(signo); // manage signal before we send it to the preforked pool of children... UProcess::kill(0, signo); // signo is sent to every process in the process group of the calling process... #ifndef USE_LIBEVENT UInterrupt::insert(signo, handler); // async signal #else UInterrupt::setHandlerForSignal(signo, handler); // sync signal #endif #if defined(U_LINUX) && defined(ENABLE_THREAD) if (u_pthread_time) ((UTimeThread*)u_pthread_time)->resume(); # if defined(USE_LIBSSL) && !defined(OPENSSL_NO_OCSP) && defined(SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB) if (pthread_ocsp) pthread_ocsp->resume(); # endif # ifdef U_SSE_ENABLE // SERVER SENT EVENTS (SSE) if (pthread_sse) pthread_sse->resume(); # endif #endif } RETSIGTYPE UServer_Base::handlerForSigHUP(int signo) { U_TRACE(0, "[SIGHUP] UServer_Base::handlerForSigHUP(%d)", signo) #if defined(ENABLE_THREAD) && !defined(USE_LIBEVENT) && defined(U_SERVER_THREAD_APPROACH_SUPPORT) if (preforked_num_kids == -1) return; #endif #ifndef U_LOG_DISABLE if (isLog()) // NB: for logrotate... { logMemUsage("SIGHUP"); log->reopen(); } #endif if (isOtherLog()) reopenLog(); u_gettimenow(); sendSignalToAllChildren(SIGTERM, (sighandler_t)UServer_Base::handlerForSigTERM); if (preforked_num_kids > 1) rkids = 0; else manageSigHUP(); } /* RETSIGTYPE UServer_Base::handlerForSigCHLD(int signo) { U_TRACE(0, "[SIGCHLD] UServer_Base::handlerForSigCHLD(%d)", signo) U_INTERNAL_ASSERT_POINTER(proc) if (proc->parent()) proc->wait(); } */ U_NO_EXPORT void UServer_Base::manageCommand(const char* format, uint32_t fmt_size, ...) { U_TRACE(0, "UServer_Base::manageCommand(%.*S,%u)", fmt_size, format, fmt_size) U_INTERNAL_ASSERT_POINTER(format) static int fd_stderr; if (fd_stderr == 0) fd_stderr = UServices::getDevNull("/tmp/command.err"); UString cmd(U_CAPACITY); va_list argp; va_start(argp, fmt_size); cmd.vsnprintf(format, fmt_size, argp); va_end(argp); UString output = UCommand::outputCommand(cmd, U_NULLPTR, -1, fd_stderr); logCommandMsgError(cmd.data(), true); if (UCommand::exit_value) U_WARNING("command failed: EXIT_VALUE=%d OUTPUT=%V", UCommand::exit_value, output.rep); (void) proc->waitAll(); } RETSIGTYPE UServer_Base::handlerForSigTERM(int signo) { U_TRACE(0, "[SIGTERM] UServer_Base::handlerForSigTERM(%d)", signo) flag_loop = false; #if defined(U_LINUX) && defined(ENABLE_THREAD) U_SRV_FLAG_SIGTERM = #endif UNotifier::flag_sigterm = true; U_INTERNAL_ASSERT_POINTER(proc) if (proc->parent()) { suspendThread(); // NB: we can't use UInterrupt::erase() because it restore the old action (UInterrupt::init)... UInterrupt::setHandlerForSignal(SIGTERM, (sighandler_t)SIG_IGN); UProcess::kill(0, SIGTERM); // SIGTERM is sent to every process in the process group of the calling process... } else { # ifdef USE_LIBEVENT (void) UDispatcher::exit(0); # elif !defined(USE_RUBY) UInterrupt::erase(SIGTERM); // async signal # endif if (preforked_num_kids) { # if defined(ENABLE_THREAD) && !defined(USE_LIBEVENT) && defined(U_SERVER_THREAD_APPROACH_SUPPORT) if (preforked_num_kids == -1) UNotifier::pthread->suspend(); # ifdef DEBUG u_trace_unlock(); # endif # endif # ifndef U_LOG_DISABLE if (isLog()) logMemUsage("SIGTERM"); # endif U_EXIT(0); } } } U_NO_EXPORT bool UServer_Base::clientImageHandlerRead() { U_TRACE_NO_PARAM(0, "UServer_Base::clientImageHandlerRead()") U_INTERNAL_ASSERT_POINTER(csocket) U_INTERNAL_ASSERT(csocket->isOpen()) U_INTERNAL_ASSERT_EQUALS(csocket, pClientImage->socket) if (pClientImage->handlerRead() == U_NOTIFIER_DELETE) { if (csocket->isOpen()) { csocket->iState = USocket::CONNECT; csocket->close(); } pClientImage->UClientImage_Base::handlerDelete(); U_RETURN(false); } U_RETURN(true); } #ifndef U_LOG_DISABLE void UServer_Base::logNewClient(USocket* psocket, UClientImage_Base* lClientImage) { U_TRACE(0, "UServer_Base::logNewClient(%p,%p)", psocket, lClientImage) if (isLog()) { # ifdef USE_LIBSSL if (bssl) lClientImage->logCertificate(); # endif USocketExt::setRemoteInfo(psocket, *(lClientImage->logbuf)); U_INTERNAL_ASSERT(lClientImage->logbuf->isNullTerminated()) char buffer[32]; uint32_t len = setNumConnection(buffer); log->log(U_CONSTANT_TO_PARAM("New client connected from %v, %.*s clients currently connected"), lClientImage->logbuf->rep, len, buffer); # ifdef U_WELCOME_SUPPORT if (msg_welcome) log->log(U_CONSTANT_TO_PARAM("Sending welcome message to %v"), lClientImage->logbuf->rep); # endif } } #endif #if defined(ENABLE_THREAD) && !defined(USE_LIBEVENT) && defined(U_SERVER_THREAD_APPROACH_SUPPORT) # define CSOCKET psocket # define CLIENT_IMAGE lClientIndex # define CLIENT_ADDRESS lclient_address # define CLIENT_ADDRESS_LEN lclient_address_len # define CLIENT_IMAGE_HANDLER_READ (pClientImage = lClientIndex, csocket = psocket, \ client_address = lclient_address, client_address_len = lclient_address_len, clientImageHandlerRead()) #else # define CSOCKET csocket # define CLIENT_IMAGE pClientImage # define CLIENT_ADDRESS client_address # define CLIENT_ADDRESS_LEN client_address_len # define CLIENT_IMAGE_HANDLER_READ clientImageHandlerRead() #endif // This method is called to accept a new connection on the server socket (listening). // Let all the process race to call accept() on the socket; since the latter is // non-blocking, that will effectively act as load balancing int UServer_Base::handlerRead() { U_TRACE_NO_PARAM(1, "UServer_Base::handlerRead()") U_INTERNAL_DUMP("nClientIndex = %u", nClientIndex) U_INTERNAL_ASSERT_MINOR(nClientIndex, UNotifier::max_connection) // This loops until the accept() fails, trying to start new connections as fast as possible so we don't overrun the listen queue pClientImage = vClientImage + nClientIndex; #if defined(ENABLE_THREAD) && !defined(USE_LIBEVENT) && defined(U_SERVER_THREAD_APPROACH_SUPPORT) USocket* psocket; uint32_t lclient_address_len = 0; char* lclient_address = U_NULLPTR; UClientImage_Base* lClientIndex = pClientImage; #endif int cround = 0; #ifdef DEBUG CLIENT_ADDRESS_LEN = 0; uint32_t numc, nothing = 0; #endif loop: U_INTERNAL_ASSERT_MINOR(CLIENT_IMAGE, eClientImage) U_INTERNAL_ASSERT_DIFFERS(U_ClientImage_parallelization, U_PARALLELIZATION_CHILD) CSOCKET = CLIENT_IMAGE->socket; U_INTERNAL_DUMP("\n----------------------------------------\n" "vClientImage[%u].last_event = %#3D\n" "vClientImage[%u].sfd = %d\n" "vClientImage[%u].UEventFd::fd = %d\n" "vClientImage[%u].socket = %p\n" "vClientImage[%u].socket->flags = %u %B\n" "vClientImage[%u].socket->iSockDesc = %d" "\n----------------------------------------\n", (CLIENT_IMAGE - vClientImage), CLIENT_IMAGE->last_event, (CLIENT_IMAGE - vClientImage), CLIENT_IMAGE->sfd, (CLIENT_IMAGE - vClientImage), CLIENT_IMAGE->UEventFd::fd, (CLIENT_IMAGE - vClientImage), CSOCKET, (CLIENT_IMAGE - vClientImage), CSOCKET->flags, CSOCKET->flags, (CLIENT_IMAGE - vClientImage), CSOCKET->iSockDesc) if (CSOCKET->isOpen()) // busy { if (cround >= 2) // polling mode { if (CLIENT_IMAGE_HANDLER_READ == false) { U_INTERNAL_DUMP("cround = %u UNotifier::num_connection = %u num_client_threshold = %u", cround, UNotifier::num_connection, num_client_threshold) if (UNotifier::num_connection < num_client_threshold) { U_DEBUG("It has returned below the client threshold(%u): preallocation(%u) num_connection(%u)", num_client_threshold, UNotifier::max_connection, UNotifier::num_connection - UNotifier::min_connection) goto end; } } # ifdef DEBUG else if (UClientImage_Base::rbuffer->empty()) { ++nothing; U_INTERNAL_DUMP("nothing = %u", nothing) } # endif } else if (ptime) // NB: we check if the connection is idle... { U_gettimeofday // NB: optimization if it is enough a time resolution of one second... if ((u_now->tv_sec - CLIENT_IMAGE->last_event) >= ptime->UTimeVal::tv_sec) { # if !defined(U_LOG_DISABLE) || (!defined(USE_LIBEVENT) && defined(HAVE_EPOLL_WAIT) && defined(DEBUG)) called_from_handlerTime = false; # endif if (handlerTimeoutConnection(CLIENT_IMAGE)) { UNotifier::handlerDelete((UEventFd*)CLIENT_IMAGE); goto try_accept; } } } try_next: if (++CLIENT_IMAGE >= eClientImage) { U_INTERNAL_ASSERT_POINTER(vClientImage) CLIENT_IMAGE = vClientImage; if (cround >= 2) { # ifdef DEBUG if (nothing > ((UNotifier::num_connection - UNotifier::min_connection) / 2)) { U_WARNING("Polling mode suspect: cround = %u nothing = %u num_connection = %u", cround, nothing, UNotifier::num_connection - UNotifier::min_connection); } nothing = 0; # endif } else if (++cround >= 2) { U_INTERNAL_DUMP("cround = %u num_client_threshold = %u (UNotifier::num_connection * 2) / 3 = %u", cround, num_client_threshold, (UNotifier::num_connection * 2) / 3) num_client_threshold = (UNotifier::num_connection * 2) / 3; U_DEBUG("Out of space on client image preallocation(%u): num_connection(%u)", UNotifier::max_connection, UNotifier::num_connection - UNotifier::min_connection) } } goto loop; } if (cround >= 2) goto try_next; // polling mode try_accept: U_INTERNAL_ASSERT(CSOCKET->isClosed()) U_INTERNAL_ASSERT_DIFFERS(U_ClientImage_parallelization, U_PARALLELIZATION_CHILD) if (socket->acceptClient(CSOCKET) == false) { U_INTERNAL_DUMP("flag_loop = %b CSOCKET->iState = %d", flag_loop, CSOCKET->iState) # ifdef DEBUG if (CLIENT_ADDRESS_LEN == 0 && CSOCKET->iState == -EAGAIN) { ++wakeup_for_nothing; } # endif # ifndef U_LOG_DISABLE if (isLog() && flag_loop && // NB: we check to avoid SIGTERM event... CSOCKET->iState != -EINTR && // NB: we check to avoid log spurious EINTR on accept() by any timer... CSOCKET->iState != -EAGAIN) { CSOCKET->setMsgError(); if (u_buffer_len) { log->log(U_CONSTANT_TO_PARAM("WARNING: accept new client failed %.*S"), u_buffer_len, u_buffer); u_buffer_len = 0; } } # endif # if defined(U_EPOLLET_POSTPONE_STRATEGY) if (CSOCKET->iState == -EAGAIN) U_ClientImage_state = U_PLUGIN_HANDLER_AGAIN; # endif goto end; } #if !defined(U_SERVER_CAPTIVE_PORTAL) || !defined(ENABLE_THREAD) setClientAddress(CSOCKET, CLIENT_ADDRESS, CLIENT_ADDRESS_LEN); # ifdef U_EVASIVE_SUPPORT if (checkHold(CSOCKET->getClientAddress())) { CSOCKET->abortive_close(); # if defined(ENABLE_THREAD) && !defined(USE_LIBEVENT) && defined(U_SERVER_THREAD_APPROACH_SUPPORT) if (preforked_num_kids != -1) # endif { U_INTERNAL_ASSERT_DIFFERS(socket_flags & O_NONBLOCK, 0) # ifndef USE_LIBEVENT goto try_next; # endif } goto next; } # endif #endif #if defined(_MSWINDOWS_) && !defined(USE_LIBEVENT) if (CSOCKET->iSockDesc >= FD_SETSIZE) { CSOCKET->abortive_close(); U_SRV_LOG("WARNING: new client connected from %.*S, connection denied by FD_SETSIZE(%u)", CLIENT_ADDRESS_LEN, CLIENT_ADDRESS, FD_SETSIZE); goto end; } #endif #ifdef U_ACL_SUPPORT if (vallow_IP && UIPAllow::isAllowed(CSOCKET->getClientAddress(), *vallow_IP) == false) { CSOCKET->abortive_close(); /** * Instructs server to accept connections from the IP address IPADDR. A CIDR mask length can be supplied optionally after * a trailing slash, e.g. 192.168.0.0/24, in which case addresses that match in the most significant MASK bits will be allowed. * If no options are specified, all clients are allowed. Unauthorized connections are rejected by closing the TCP connection * immediately. A warning is logged on the server but nothing is sent to the client */ U_SRV_LOG("WARNING: new client connected from %.*S, connection denied by Access Control List", CLIENT_ADDRESS_LEN, CLIENT_ADDRESS); # if defined(ENABLE_THREAD) && !defined(USE_LIBEVENT) && defined(U_SERVER_THREAD_APPROACH_SUPPORT) if (preforked_num_kids != -1) # endif { U_INTERNAL_ASSERT_DIFFERS(socket_flags & O_NONBLOCK, 0) # ifndef USE_LIBEVENT goto try_next; # endif } goto next; } #endif #ifdef U_RFC1918_SUPPORT if (public_address && enable_rfc1918_filter && CSOCKET->remoteIPAddress().isPrivate() && (vallow_IP_prv == U_NULLPTR || UIPAllow::isAllowed(CSOCKET->getClientAddress(), *vallow_IP_prv) == false)) { CSOCKET->abortive_close(); U_SRV_LOG("WARNING: new client connected from %.*S, connection denied by RFC1918 filtering (reject request from private IP to public server address)", CLIENT_ADDRESS_LEN, CLIENT_ADDRESS); # if defined(ENABLE_THREAD) && !defined(USE_LIBEVENT) && defined(U_SERVER_THREAD_APPROACH_SUPPORT) if (preforked_num_kids != -1) # endif { U_INTERNAL_ASSERT_DIFFERS(socket_flags & O_NONBLOCK, 0) # ifndef USE_LIBEVENT goto try_next; # endif } goto next; } #endif #if !defined(U_LOG_DISABLE) && defined(U_LINUX) && defined(ENABLE_THREAD) ULock::atomicIncrement(U_SRV_TOT_CONNECTION); U_INTERNAL_DUMP("U_SRV_TOT_CONNECTION = %u", U_SRV_TOT_CONNECTION) #endif ++UNotifier::num_connection; #ifdef DEBUG ++stats_connections; numc = UNotifier::num_connection - UNotifier::min_connection; if (max_depth < numc) max_depth = numc; if (stats_simultaneous < numc) stats_simultaneous = numc; U_INTERNAL_DUMP("numc = %u max_depth = %u stats_simultaneous = %u", numc, max_depth, stats_simultaneous) #endif /** * PREFORK_CHILD number of child server processes created at startup: * * -1 - thread approach (experimental) * 0 - serialize, no forking * 1 - classic, forking after accept client * >1 - pool of process serialize plus monitoring process */ #ifdef U_CLASSIC_SUPPORT if (isClassic()) { if (proc->fork() && proc->parent()) { int pid, status; CSOCKET->close(); U_SRV_LOG("Started new child (pid %d), up to %u children", proc->pid(), UNotifier::num_connection - UNotifier::min_connection); retry: pid = UProcess::waitpid(-1, &status, WNOHANG); // NB: to avoid too much zombie... if (pid > 0) { --UNotifier::num_connection; # ifndef U_LOG_DISABLE if (isLog()) { char buffer[128]; log->log(U_CONSTANT_TO_PARAM("Child (pid %d) exited with value %d (%s), down to %u children"), pid, status, UProcess::exitInfo(buffer, status), UNotifier::num_connection - UNotifier::min_connection); } # endif goto retry; } U_SRV_LOG("Waiting for connection on port %u", port); U_RETURN(U_NOTIFIER_OK); } if (proc->child()) { // --------------------------------------------------------------------------------------------------------- // NB: the forked child don't accept new client, but we need anyway the event manager because // the forked child must feel the possibly timeout for request from the new client... // --------------------------------------------------------------------------------------------------------- uint32_t num_connection_save = UNotifier::num_connection; UNotifier::num_connection = 0; UNotifier::init(); UNotifier::num_connection = num_connection_save; } } #endif #ifndef U_LOG_DISABLE logNewClient(CSOCKET, CLIENT_IMAGE); #endif #ifdef U_WELCOME_SUPPORT if (msg_welcome && USocketExt::write(CSOCKET, *msg_welcome, timeoutMS) == false) { CSOCKET->abortive_close(); CLIENT_IMAGE->UClientImage_Base::handlerDelete(); goto next; } #endif #if defined(ENABLE_THREAD) && !defined(USE_LIBEVENT) && defined(U_SERVER_THREAD_APPROACH_SUPPORT) if (preforked_num_kids == -1) lClientIndex->UEventFd::fd = psocket->iSockDesc; else #endif { #ifdef DEBUG uint32_t len = 0; int cpu = U_SYSCALL_NO_PARAM(sched_getcpu), scpu = -1; # ifdef SO_INCOMING_CPU if (USocket::bincoming_cpu) { len = sizeof(socklen_t); (void) CSOCKET->getSockOpt(SOL_SOCKET, SO_INCOMING_CPU, (void*)&scpu, len); len = (USocket::incoming_cpu == scpu ? 0 : U_CONSTANT_SIZE(" [DIFFER]")); } # endif U_INTERNAL_DUMP("USocket::incoming_cpu = %d USocket::bincoming_cpu = %b sched cpu = %d socket cpu = %d", USocket::incoming_cpu, USocket::bincoming_cpu, cpu, scpu) if (len) U_DEBUG("UServer_Base::handlerRead(): CPU: %d sched(%d) socket(%d)%.*s", USocket::incoming_cpu, cpu, scpu, len, " [DIFFER]") #endif if (CLIENT_IMAGE_HANDLER_READ == false) goto next; } U_INTERNAL_ASSERT(CSOCKET->isOpen()) U_INTERNAL_ASSERT_DIFFERS(U_ClientImage_parallelization, U_PARALLELIZATION_CHILD) #if defined(HAVE_EPOLL_CTL_BATCH) && !defined(USE_LIBEVENT) UNotifier::batch((UEventFd*)CLIENT_IMAGE); #else UNotifier::insert((UEventFd*)CLIENT_IMAGE); #endif if (++CLIENT_IMAGE >= eClientImage) CLIENT_IMAGE = vClientImage; next: #ifdef USE_LIBEVENT goto end; #endif #if defined(ENABLE_THREAD) && defined(U_SERVER_THREAD_APPROACH_SUPPORT) if (preforked_num_kids != -1) #endif { U_INTERNAL_ASSERT_DIFFERS(socket_flags & O_NONBLOCK, 0) U_INTERNAL_DUMP("cround = %u UNotifier::num_connection = %u num_client_threshold = %u", cround, UNotifier::num_connection, num_client_threshold) if (num_client_threshold > UNotifier::num_connection) cround = 0; else { cround = 2; CLIENT_IMAGE = vClientImage; U_DEBUG("It has passed the client threshold(%u): preallocation(%u) num_connection(%u)", num_client_threshold, UNotifier::max_connection, UNotifier::num_connection - UNotifier::min_connection) } goto loop; } end: #if defined(HAVE_EPOLL_CTL_BATCH) && !defined(USE_LIBEVENT) UNotifier::insertBatch(); #endif nClientIndex = CLIENT_IMAGE - vClientImage; U_INTERNAL_DUMP("nClientIndex = %u", nClientIndex) U_INTERNAL_ASSERT_MINOR(nClientIndex, UNotifier::max_connection) U_RETURN(U_NOTIFIER_OK); } #undef CSOCKET #undef CLIENT_IMAGE #undef CLIENT_ADDRESS #undef CLIENT_ADDRESS_LEN #undef CLIENT_IMAGE_HANDLER_READ #if !defined(U_LOG_DISABLE) uint32_t UServer_Base::setNumConnection(char* ptr) { U_TRACE(0, "UServer_Base::setNumConnection(%p)", ptr) # ifdef USERVER_UDP *ptr = '0'; U_RETURN(1); # else uint32_t len, sz = UNotifier::num_connection - UNotifier::min_connection - 1; if (preforked_num_kids <= 0) len = u_num2str32(sz, ptr) - ptr; else { char* start = ptr; *ptr = '('; ptr = u_num2str32(sz, ptr+1); *ptr = '/'; # if defined(U_LINUX) && defined(ENABLE_THREAD) len = U_SRV_TOT_CONNECTION; U_INTERNAL_DUMP("len = %d", len) if ((int32_t)len < 0) U_SRV_TOT_CONNECTION = len = 0; else if (len >= 1 && flag_loop) // NB: check for SIGTERM event... { --len; } # else len = 0; # endif ptr = u_num2str32(len, ptr+1); *ptr = ')'; len = ptr-start+1; } U_RETURN(len); # endif } #endif bool UServer_Base::handlerTimeoutConnection(void* cimg) { U_TRACE(0, "UServer_Base::handlerTimeoutConnection(%p)", cimg) U_INTERNAL_ASSERT_POINTER(cimg) U_INTERNAL_ASSERT_POINTER(pthis) U_INTERNAL_ASSERT_POINTER(ptime) U_INTERNAL_ASSERT_DIFFERS(timeoutMS, -1) U_INTERNAL_DUMP("pthis = %p handler_other = %p handler_inotify = %p", pthis, handler_other, handler_inotify) if (cimg == pthis || cimg == handler_other || cimg == handler_inotify) { U_RETURN(false); } if (((UClientImage_Base*)cimg)->handlerTimeout() == U_NOTIFIER_DELETE) { # ifndef U_LOG_DISABLE if (isLog()) { if (called_from_handlerTime) { log->log(U_CONSTANT_TO_PARAM("handlerTime: client connected didn't send any request in %u secs (timeout), close connection %v"), ptime->UTimeVal::tv_sec, ((UClientImage_Base*)cimg)->logbuf->rep); } else { log->log(U_CONSTANT_TO_PARAM("handlerTimeoutConnection: client connected didn't send any request in %u secs, close connection %v"), UNotifier::last_event - ((UClientImage_Base*)cimg)->last_event, ((UClientImage_Base*)cimg)->logbuf->rep); } } # endif # if !defined(USE_LIBEVENT) && defined(HAVE_EPOLL_WAIT) && defined(DEBUG) if (called_from_handlerTime) { U_DEBUG("handlerTime: client connected didn't send any request in %u secs (timeout %u sec) - " "UEventFd::fd = %d socket->iSockDesc = %d UNotifier::num_connection = %d UNotifier::min_connection = %d", UNotifier::last_event - ((UClientImage_Base*)cimg)->last_event, ptime->UTimeVal::tv_sec, ((UClientImage_Base*)cimg)->UEventFd::fd, ((UClientImage_Base*)cimg)->socket->iSockDesc, UNotifier::num_connection, UNotifier::min_connection) } else { U_DEBUG("handlerTimeoutConnection: client connected didn't send any request in %u secs - " "UEventFd::fd = %d socket->iSockDesc = %d UNotifier::num_connection = %d UNotifier::min_connection = %d", UNotifier::last_event - ((UClientImage_Base*)cimg)->last_event, ((UClientImage_Base*)cimg)->UEventFd::fd, ((UClientImage_Base*)cimg)->socket->iSockDesc, UNotifier::num_connection, UNotifier::min_connection) } # endif U_RETURN(true); // NB: return true mean that we want to erase the item... } U_RETURN(false); } void UServer_Base::runLoop(const char* user) { U_TRACE(0, "UServer_Base::runLoop(%S)", user) #ifdef USERVER_UDP if (budp == false) #endif { if (pluginsHandlerFork() != U_PLUGIN_HANDLER_FINISHED) U_ERROR("Plugins stage fork failed"); } #ifdef U_LINUX socket->reusePort(socket_flags); # if defined(USERVER_UDP) || defined(USERVER_IPC) if (budp == false && bipc == false) # endif { /** * Let's say an application just issued a request to send a small block of data. Now, we could * either send the data immediately or wait for more data. Some interactive and client-server * applications will benefit greatly if we send the data right away. For example, when we are * sending a short request and awaiting a large response, the relative overhead is low compared * to the total amount of data transferred, and the response time could be much better if the * request is sent immediately. This is achieved by setting the TCP_NODELAY option on the socket, * which disables the Nagle algorithm. * * Linux (along with some other OSs) includes a TCP_DEFER_ACCEPT option in its TCP implementation. * Set on a server-side listening socket, it instructs the kernel not to wait for the final ACK packet * and not to initiate the process until the first packet of real data has arrived. After sending the SYN/ACK, * the server will then wait for a data packet from a client. Now, only three packets will be sent over the * network, and the connection establishment delay will be significantly reduced, which is typical for HTTP. * NB: Takes an integer value (seconds) * * Another way to prevent delays caused by sending useless packets is to use the TCP_QUICKACK option. * This option is different from TCP_DEFER_ACCEPT, as it can be used not only to manage the process of * connection establishment, but it can be used also during the normal data transfer process. In addition, * it can be set on either side of the client-server connection. Delaying sending of the ACK packet could * be useful if it is known that the user data will be sent soon, and it is better to set the ACK flag on * that data packet to minimize overhead. When the sender is sure that data will be immediately be sent * (multiple packets), the TCP_QUICKACK option can be set to 0. The default value of this option is 1 for * sockets in the connected state, which will be reset by the kernel to 1 immediately after the first use. * (This is a one-time option) */ if (tcp_linger_set > -2) socket->setTcpLinger(tcp_linger_set); # if !defined(U_SERVER_CAPTIVE_PORTAL) || defined(ENABLE_THREAD) socket->setTcpNoDelay(); socket->setTcpFastOpen(); socket->setTcpDeferAccept(); if (bssl == false) socket->setBufferSND(500 * 1024); // 500k: for major size we assume is better to use sendfile() if (set_tcp_keep_alive) socket->setTcpKeepAlive(); # endif } if (user) { if (u_runAsUser(user, false) == false) U_ERROR("Set user %S context failed", user); U_SRV_LOG("Server run with user %S permission", user); } else if (USocket::iBackLog != 1) { // We don't need these anymore. // Good security policy says we get rid of them u_never_need_root(); u_never_need_group(); } #endif pthis->UEventFd::fd = socket->iSockDesc; #ifdef USERVER_UDP if (budp == false) #endif { UNotifier::init(); U_INTERNAL_DUMP("UNotifier::min_connection = %d", UNotifier::min_connection) if (UNotifier::min_connection) { if (binsert) UNotifier::insert(pthis, EPOLLEXCLUSIVE | EPOLLROUNDROBIN); // NB: we ask to be notified for request of connection (=> accept) if (handler_other) UNotifier::insert(handler_other, EPOLLEXCLUSIVE | EPOLLROUNDROBIN); // NB: we ask to be notified for request from generic system if (handler_inotify) UNotifier::insert(handler_inotify, EPOLLEXCLUSIVE | EPOLLROUNDROBIN); // NB: we ask to be notified for change of file system (=> inotify) } #if defined(ENABLE_THREAD) && !defined(USE_LIBEVENT) && defined(U_SERVER_THREAD_APPROACH_SUPPORT) U_INTERNAL_ASSERT_EQUALS(UNotifier::pthread, U_NULLPTR) if (preforked_num_kids == -1) { U_NEW(UClientThread, UNotifier::pthread, UClientThread); # ifdef _MSWINDOWS_ InitializeCriticalSection(&UNotifier::mutex); # endif UNotifier::pthread->start(50); proc->_pid = UNotifier::pthread->id; U_ASSERT(proc->parent()) } #endif } U_SRV_LOG("Waiting for connection on port %u", port); #if defined(U_LINUX) && defined(ENABLE_THREAD) (void) U_SYSCALL(pthread_sigmask, "%d,%p,%p", SIG_UNBLOCK, &mask, U_NULLPTR); #endif if (ptime) { UTimer::insert(ptime); # if !defined(U_LOG_DISABLE) && defined(DEBUG) last_event = u_now->tv_sec; # endif } #ifdef USERVER_UDP if (budp) { struct stat st; char buffer[U_PATH_MAX+1]; HINSTANCE handle = U_NULLPTR; uint32_t len = u__snprintf(buffer, U_PATH_MAX, U_CONSTANT_TO_PARAM(U_LIBEXECDIR "/usp/udp.%s"), U_LIB_SUFFIX); if (U_SYSCALL(stat, "%S,%p", buffer, &st)) { U_WARNING("I can't found the usp page: %.*S", len, buffer); } else { if ((handle = UDynamic::dload(buffer)) == U_NULLPTR || (runDynamicPage_udp = (vPFi)UDynamic::lookup(handle, "runDynamicPage_udp")) == U_NULLPTR) { U_WARNING("load failed of usp page: %.*S", len, buffer); } else { runDynamicPage_udp(U_DPAGE_INIT); runDynamicPage_udp(U_DPAGE_FORK); } } csocket = pClientImage->socket = socket; pClientImage->UEventFd::fd = socket->iSockDesc; UClientImage_Base::callerHandlerRead = UServer_Base::handlerUDP; U_INTERNAL_DUMP("handler_other = %p handler_inotify = %p UNotifier::num_connection = %u UNotifier::min_connection = %u", handler_other, handler_inotify, UNotifier::num_connection, UNotifier::min_connection) // NB: we can go directly on recvFrom() and block on it... U_INTERNAL_ASSERT_EQUALS(socket_flags & O_NONBLOCK, 0) while (flag_loop) { if (pClientImage->handlerRead() == U_NOTIFIER_DELETE) break; # ifndef U_LOG_DISABLE if (isLog()) { pClientImage->logbuf->setEmpty(); log->log(U_CONSTANT_TO_PARAM("Waiting for connection on port %u"), port); } # endif } if (runDynamicPage_udp) { runDynamicPage_udp(U_DPAGE_DESTROY); UDynamic::dclose(handle); } } else #endif { while (flag_loop) { U_INTERNAL_DUMP("handler_other = %p handler_inotify = %p UNotifier::num_connection = %u UNotifier::min_connection = %u", handler_other, handler_inotify, UNotifier::num_connection, UNotifier::min_connection) # if defined(ENABLE_THREAD) && !defined(USE_LIBEVENT) && defined(U_SERVER_THREAD_APPROACH_SUPPORT) if (preforked_num_kids != -1) # endif { if (UNotifier::min_connection) // NB: we need to notify something to someone... { UNotifier::waitForEvent(); if (ptime && UNotifier::nfd_ready > 0) { # if !defined(U_LOG_DISABLE) && defined(DEBUG) last_event = u_now->tv_sec; # ifndef _MSWINDOWS_ if (monitoring_process && U_SYSCALL_NO_PARAM(getppid) == 1) { U_ERROR("the monitoring process has crashed, exiting..."); } # endif # endif UTimer::updateTimeToExpire(ptime); } U_ASSERT_EQUALS(UNotifier::empty(), false) continue; } } // NB: we can go directly on accept() and block on it... U_INTERNAL_ASSERT_EQUALS(socket_flags & O_NONBLOCK, 0) # if !defined(ENABLE_THREAD) || defined(USE_LIBEVENT) || !defined(U_SERVER_THREAD_APPROACH_SUPPORT) U_INTERNAL_ASSERT(UNotifier::min_connection == UNotifier::num_connection) # endif (void) pthis->UServer_Base::handlerRead(); } } } void UServer_Base::run() { U_TRACE_NO_PARAM(1, "UServer_Base::run()") init(); int status; uint32_t crash_counter = 0; bool baffinity = false, bmail; UTimeVal to_sleep(0L, 500L * 1000L); const char* user = (as_user->empty() ? U_NULLPTR : as_user->data()); UHttpClient_Base::server_context_flag = true; /** * PREFORK_CHILD number of child server processes created at startup: * * -1 - thread approach (experimental) * 0 - serialize, no forking * 1 - classic, forking after accept client * >1 - pool of process serialize plus monitoring process */ # if !defined(U_SERVER_CAPTIVE_PORTAL) || defined(ENABLE_THREAD) if (preforked_num_kids > 1) monitoring_process = true; else if (monitoring_process == false) { runLoop(user); goto stop; } #else monitoring_process = true; #endif /** * Main loop for the parent process with the new preforked implementation. * The parent is just responsible for keeping a pool of children and they accept connections themselves... */ U_INTERNAL_DUMP("preforked_num_kids = %d monitoring_process = %b", preforked_num_kids, monitoring_process) int nkids; cpu_set_t cpuset; pid_t pid, pid_to_wait; #ifdef HAVE_SCHED_GETAFFINITY if (u_get_num_cpu() > 1 && (preforked_num_kids % u_num_cpu) == 0) { baffinity = true; U_SRV_LOG("cpu affinity is to be set; thread count (%u) multiple of cpu count (%u)", preforked_num_kids, u_num_cpu); } #endif U_INTERNAL_ASSERT_EQUALS(rkids, 0) nkids = (preforked_num_kids <= 0 ? 1 : (pid_to_wait = -1, preforked_num_kids)); U_INTERNAL_DUMP("nkids = %u", nkids) while (flag_loop) { u_need_root(false); while (rkids < nkids) { if (proc->fork() && proc->parent()) { ++rkids; if (preforked_num_kids <= 0) pid_to_wait = proc->_pid; U_SRV_LOG("Started new child (pid %d), up to %u children", proc->_pid, rkids); U_INTERNAL_DUMP("up to %u children, UNotifier::num_connection = %d", rkids, UNotifier::num_connection) } if (proc->child()) { # ifdef U_SSE_ENABLE // SERVER SENT EVENTS (SSE) UFile::close(sse_socketpair[0]); # endif U_INTERNAL_DUMP("child = %P UNotifier::num_connection = %d", UNotifier::num_connection) # ifdef HAVE_SCHED_GETAFFINITY if (baffinity) { CPU_ZERO(&cpuset); u_bind2cpu(&cpuset, rkids % u_num_cpu); // Pin the process to a particular cpu... # ifdef SO_INCOMING_CPU USocket::incoming_cpu = rkids % u_num_cpu; # endif # ifndef U_LOG_DISABLE if (isLog()) { uint32_t sz; char buffer[64]; int cpu = U_SYSCALL_NO_PARAM(sched_getcpu); # ifdef HAVE_SCHED_GETCPU if (USocket::incoming_cpu != cpu && USocket::incoming_cpu != -1) { sz = u__snprintf(buffer, U_CONSTANT_SIZE(buffer), U_CONSTANT_TO_PARAM(" (EXPECTED CPU %d)"), USocket::incoming_cpu); } else # endif sz = 0; log->log(U_CONSTANT_TO_PARAM("New child started, affinity mask: %x, cpu: %d%.*s"), CPUSET_BITS(&cpuset)[0], cpu, sz, buffer); } # endif } # ifdef HAVE_LIBNUMA if (U_SYSCALL_NO_PARAM(numa_max_node)) { struct bitmask* bmask = (struct bitmask*) U_SYSCALL(numa_bitmask_alloc, "%u", 16); (void) U_SYSCALL(numa_bitmask_setbit, "%p,%u", bmask, rkids % 2); U_SYSCALL_VOID(numa_set_membind, "%p", bmask); U_SYSCALL_VOID(numa_bitmask_free, "%p", bmask); } # endif if (set_realtime_priority) u_switch_to_realtime_priority(); # endif /** * POSIX.1-1990 disallowed setting the action for SIGCHLD to SIG_IGN. * POSIX.1-2001 allows this possibility, so that ignoring SIGCHLD can be * used to prevent the creation of zombies (see wait(2)). Nevertheless, the * historical BSD and System V behaviors for ignoring SIGCHLD differ, so that * the only completely portable method of ensuring that terminated children do * not become zombies is to catch the SIGCHLD signal and perform a wait(2) or similar. * * NB: we cannot use SIGCHLD to avoid zombie for parallelization because in this way we * interfere with waiting of cgi-bin processing that write on pipe and also to know * exit status from script... * * UInterrupt::setHandlerForSignal(SIGCHLD, (sighandler_t)UServer_Base::handlerForSigCHLD); */ UInterrupt::setHandlerForSignal(SIGHUP, (sighandler_t)SIG_IGN); // NB: we can't use UInterrupt::erase() because it restore the old action (UInterrupt::init)... # ifdef U_LINUX (void) U_SYSCALL(prctl, "%d,%lu", PR_SET_PDEATHSIG, SIGTERM); # ifdef DEBUG u_trace_unlock(); # endif # endif runLoop(user); return; } // Don't start them too quickly, or we might overwhelm a machine that's having trouble to_sleep.nanosleep(); # if defined(U_SERVER_CAPTIVE_PORTAL) && !defined(ENABLE_THREAD) if (proc->_pid == -1) // If the child don't start (not enough memory) we disable the monitoring process... { U_INTERNAL_ASSERT(monitoring_process) monitoring_process = false; runLoop(user); goto stop; } # endif } // wait for any children to exit, and then start some more # if defined(U_LINUX) && defined(ENABLE_THREAD) (void) U_SYSCALL(pthread_sigmask, "%d,%p,%p", SIG_UNBLOCK, &mask, U_NULLPTR); # endif u_dont_need_root(); pid = UProcess::waitpid(pid_to_wait, &status, 0); U_INTERNAL_DUMP("rkids = %d", rkids) if (rkids == 0) // NB: check for SIGHUP event... { manageSigHUP(); continue; } if (pid > 0 && flag_loop) // NB: check for SIGTERM event... { U_INTERNAL_ASSERT_MAJOR(rkids, 0) --rkids; baffinity = false; U_INTERNAL_DUMP("down to %u children", rkids) // Another little safety brake here: since children should not // exit too quickly, pausing before starting them should be harmless if (USemaphore::checkForDeadLock(to_sleep) == false) to_sleep.nanosleep(); # ifndef U_LOG_DISABLE if (isLog()) { char buffer[128]; log->log(U_CONSTANT_TO_PARAM("WARNING: child (pid %d) exited with value %d (%s), down to %u children"), pid, status, UProcess::exitInfo(buffer, status), rkids); } # endif U_DEBUG("child (pid %d) exited with value %d, down to %u children - crash_counter = %u", pid, status, rkids, crash_counter+1); bmail = false; if (crashEmailAddress && ++crash_counter > crash_count) { if ((u_now->tv_sec - last_time_email_crash) > U_ONE_DAY_IN_SECOND) { bmail = true; last_time_email_crash = u_now->tv_sec; } } if (bmail) { U_INTERNAL_ASSERT_POINTER(emailClient) pid_t lpid = startNewChild(); if (lpid > 0) continue; // parent UString body(200U); body.snprintf(U_CONSTANT_TO_PARAM("%N (on %V): number of crashes %u has exceeded the threshold %u since %#5D"), IP_address->rep, crash_counter, crash_count, u_start_time); emailClient->sendEmail(*crashEmailAddress, U_STRING_FROM_CONSTANT("number of crashes has exceeded the threshold"), body); if (lpid == 0) endNewChild(); } } } U_INTERNAL_ASSERT(proc->parent()) stop: #ifdef USERVER_UDP if (budp == false) #endif { if (pluginsHandlerStop() != U_PLUGIN_HANDLER_FINISHED) U_WARNING("Plugins stage stop failed"); } manageWaitAll(); #ifndef U_LOG_DISABLE if (isLog() && UNotifier::flag_sigterm) { logMemUsage("SIGTERM"); } #endif if (proc->parent() || preforked_num_kids <= 0) { to_sleep.nanosleep(); closeLog(); } #ifdef U_SSE_ENABLE // SERVER SENT EVENTS (SSE) if (pthread_sse) sse_vclient->clear(); #endif #ifdef DEBUG pthis->deallocate(); #endif } // it creates a copy of itself, return true if parent... pid_t UServer_Base::startNewChild() { U_TRACE_NO_PARAM(0, "UServer_Base::startNewChild()") UProcess p; if (p.fork() && p.parent()) { pid_t pid = p.pid(); # if defined(U_LOG_DISABLE) || !defined(U_LINUX) || !defined(ENABLE_THREAD) (void) UProcess::removeZombies(); # else uint32_t n = UProcess::removeZombies(); ULock::atomicIncrement(U_SRV_TOT_CONNECTION); U_SRV_LOG("Started new child (pid %d) for parallelization (%d) - removed %u zombies", pid, U_SRV_TOT_CONNECTION, n); # endif U_RETURN(pid); // parent } if (p.child()) U_RETURN(0); U_RETURN(-1); } __noreturn void UServer_Base::endNewChild() { U_TRACE_NO_PARAM(0, "UServer_Base::endNewChild()") #if !defined(U_LOG_DISABLE) && defined(U_LINUX) && defined(ENABLE_THREAD) ULock::atomicDecrement(U_SRV_TOT_CONNECTION); U_INTERNAL_DUMP("cnt_parallelization = %u U_SRV_FLAG_SIGTERM = %b", U_SRV_TOT_CONNECTION, U_SRV_FLAG_SIGTERM) if (U_SRV_FLAG_SIGTERM == false) U_SRV_LOG("child for parallelization ended (%u)", U_SRV_TOT_CONNECTION); #else U_SRV_LOG("child for parallelization ended"); #endif U_EXIT(0); } bool UServer_Base::startParallelization(uint32_t nclient) { U_TRACE(0, "UServer_Base::startParallelization(%u)", nclient) #if defined(ENABLE_THREAD) && !defined(USE_LIBEVENT) && defined(U_SERVER_THREAD_APPROACH_SUPPORT) if (preforked_num_kids != -1) #endif { if (isParallelizationGoingToStart(nclient)) { # ifdef DEBUG if (UClient_Base::csocket) { U_WARNING("after forking you can have problem with the shared db connection..."); } # endif pid_t pid = startNewChild(); if (pid > 0) { // NB: from now it is responsability of the child to services the request from the client on the same connection... csocket->close(); UClientImage_Base::resetPipelineAndSetCloseConnection(); U_ClientImage_parallelization = U_PARALLELIZATION_PARENT; U_RETURN(true); } if (pid == 0) U_ClientImage_parallelization = U_PARALLELIZATION_CHILD; } } U_RETURN(false); } // DEBUG #if defined(U_STDCPP_ENABLE) && defined(DEBUG) const char* UServer_Base::dump(bool reset) const { *UObjectIO::os << "port " << port << '\n' << "map_size " << map_size << '\n' << "flag_loop " << flag_loop << '\n' << "timeoutMS " << timeoutMS << '\n' << "verify_mode " << verify_mode << '\n' << "shared_data_add " << shared_data_add << '\n' << "ptr_shared_data " << (void*)ptr_shared_data << '\n' << "preforked_num_kids " << preforked_num_kids << '\n' << "log (ULog " << (void*)log << ")\n" << "socket (USocket " << (void*)socket << ")\n" << "host (UString " << (void*)host << ")\n" << "dh_file (UString " << (void*)dh_file << ")\n" << "ca_file (UString " << (void*)ca_file << ")\n" << "ca_path (UString " << (void*)ca_path << ")\n" << "key_file (UString " << (void*)key_file << ")\n" << "password (UString " << (void*)password << ")\n" << "cert_file (UString " << (void*)cert_file << ")\n" << "name_sock (UString " << (void*)name_sock << ")\n" << "IP_address (UString " << (void*)IP_address << ")\n" # ifdef U_WELCOME_SUPPORT << "msg_welcome (UString " << (void*)msg_welcome << ")\n" # endif << "document_root (UString " << (void*)document_root << ")\n" << "proc (UProcess " << (void*)proc << ')'; if (reset) { UObjectIO::output(); return UObjectIO::buffer_output; } return U_NULLPTR; } #endif